首页 > 解决方案 > 当“运算符”函数根据类而不是实际运算符定义时,它们如何工作?

问题描述

我正在探索关于 C++ 设计模式的在线课程,我遇到了一个使用operator函数声明的奇怪“强制转换”(?)。

最小设置如下(实际代码如下):

class A {
    ...

    static B build();
};

class B {
    A a;
};


int main()
{
    A obj = A::build();
}

由于该build函数返回一个类型为 的对象B,因此存在类型不匹配并且代码无法编译。为了纠正这个问题,教师在 B 类中定义了以下函数:

operator A() { return a; }

我的问题是,这是如何工作的?我了解重载运算符的机制,但在这种情况下,我们重载的是一个实际的类,而不是一个运算符。当我们使用另一个类声明一个操作符函数时,会发生什么?更重要的是,没有定义返回类型,编译器是否只是假设返回类型与定义函数的类相同?(即.. B operator A() { ... })直觉上,我无法真正理解这个概念。

这种方法我压根没听说过,更何况在刚刚接触之前,根本就认为是可行的。我一直试图在网上对此进行研究,但是——可以理解的是,我会说——我所有的搜索结果都返回了基本重载的链接,或者至少是更传统的重载,使用运算符。


对于上下文,本讲座是关于“构建器”设计模式,使用 Html 元素和 Html 构建器结构。这是我的基本代码,还没有修改。

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct HtmlBuilder;

struct HtmlElement {
    std::string name;
    std::string text;

    std::vector<HtmlElement> elements;

    const std::size_t indent_size = 2;

    std::string str(const int indent = 0) const {
        std::ostringstream oss;

        std::string indentation(indent_size * indent, ' ');

        oss << indentation << "<" << name << ">\n";

        if (!text.empty())
            oss << std::string(indent_size * (indent + 1), ' ') << text << '\n';

        for (const auto& element : elements)
            oss << element.str(indent + 1);

        oss << indentation << "</" << name << ">\n";

        return oss.str();
    }

    static HtmlBuilder build(const std::string& rootName);
};

struct HtmlBuilder {
    HtmlElement root;

    void addChild(const std::string& childName, const std::string& childText) {
        HtmlElement childElement { childName, childText };
        root.elements.emplace_back(childElement);
    }

    std::string str() const { return root.str(); }
};

HtmlBuilder HtmlElement::build(const std::string& rootName) {
    return { rootName };
}

int main()
{
    HtmlBuilder builder { "ul" };
    builder.addChild("li", "hello");
    builder.addChild("li", "world");

    std::cout << builder.str();
}

输出,如预期:

<ul>
  <li>
    hello
  </li>
  <li>
    world
  </li>
</ul>

在演示“流利的构建器”模式时,讲师让我们修改addChild函数以返回对构建器结构的引用。

HtmlBuilder::addChild函数修改如下:返回类型由改为void( HtmlBuilder&returning *this)

HtmlBuilder& addChild(const std::string& childName, const std::string& childText) {
    HtmlElement childElement { childName, childText };
    root.elements.emplace_back(childElement);

    return *this;
}

然后重写该main函数:

int main()
{
    auto builder = HtmlElement::build("ul").addChild("li", "hello").addChild("li", "world");

    std::cout << builder.str();
}

输出又是:

<ul>
  <li>
    hello
  </li>
  <li>
    world
  </li>
</ul>

在成功定义并实现了 fluent 构建器模式之后,讲师现在提出了以下问题:

我们如何从build函数中获取 Html 元素对象?

我的直接反应是考虑为HtmlBuilder班级提供一个 getter 方法。一些微不足道的东西,像这样:

struct HtmlBuilder {
    ...
    HtmlElement getElement() const { return root; }
};

然后,您将像这样“构建并获取”元素:

int main()
{
    const auto builder = HtmlElement::build("ul").addChild("li", "hello").addChild("li", "world");
    const auto element = builder.getElement();

    std::cout << builder.str() << '\n';
    std::cout << element.str() << '\n';
}

两个输出将是相同的。然而,导师选择了一种截然不同且更有趣的方法。他没有通过我的“构建和获取”方法分两步完成,而是执行了以下操作。

他首先像这样重写了main函数(请注意,与我不同,他一步构建和获取元素):

int main()
{
    HtmlElement element = HtmlElement::build("ul").addChild("li", "hello").addChild("li", "world");

    std::cout << element.str();
}

最初编译器拒绝此修改,因为HtmlElement::build调用的结果是一个HtmlBuilder对象。所以为了解决这个问题,导师做的第二件事就是在HtmlBuilder类中定义如下函数:

operator HtmlElement() const { return root; }

完成此操作后,代码编译顺利,应用程序输出再次为:

<ul>
  <li>
    hello
  </li>
  <li>
    world
  </li>
</ul>

同样,我的问题是,为什么或如何工作?operator当我们使用另一个类声明一个函数时正在做什么?我了解通常的运算符重载的诡计。()重载,[]或对我来说很直观=,但我不明白这种情况如何或为什么起作用。甚至没有声明的返回类型;编译器是否只是假设它旨在返回当前的类类型?

谢谢大家的时间。

标签: c++operator-keyword

解决方案


回复:“我们正在重载一个实际的类”。号operator A() { return a; }是重载运算符;注意关键字operator。这定义了一个转换运算符B,当代码调用从类型对象到类型对象的转换时将使用该运算符A

您的示例中的用法有点晦涩难懂。这是一个更简单的例子:

B b;
A obj = b;

创建obj对象需要将b对象转换为 type 的对象A,这就是要做的工作operator A()

在您的示例中,调用A::build返回 type 的对象B,因此在代码中

A obj = A::build();

调用A::build()返回一个临时类型的对象,B转换运算符 ( operator A()) 将该对象转换A为用于初始化的类型的对象obj


推荐阅读