c++ - 当“运算符”函数根据类而不是实际运算符定义时,它们如何工作?
问题描述
我正在探索关于 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
当我们使用另一个类声明一个函数时正在做什么?我了解通常的运算符重载的诡计。()
重载,[]
或对我来说很直观=
,但我不明白这种情况如何或为什么起作用。甚至没有声明的返回类型;编译器是否只是假设它旨在返回当前的类类型?
谢谢大家的时间。
解决方案
回复:“我们正在重载一个实际的类”。号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
。
推荐阅读
- javascript - express js的分页url问题
- python - python:如果目录存在,如何检查特定模式日志文件是否存在
- python-3.x - 在sqlalchemy中更新时如何绕过“关键字不能是表达式”?
- android - android在64位.so文件加载时显示空白屏幕...... 32位so文件没有问题
- azure-devops - Azure DevOps 仅在特定日期运行步骤
- java - 如何将字符转换为字符串..?
- serialization - 将未知 JSON 序列化和反序列化为二进制
- ios - Xcode 11.2.1 错误:Command CompileSwiftSources 失败,退出代码为非零
- azure - 在所有作业后任务之后运行管道任务
- php - 如何建立 Web Socket 连接?