Я изучал онлайн-курс по шаблонам проектирования в 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&
(возвращая *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>
Успешно определив и применив шаблон беглого строителя, инструктор поставил следующий вопрос:
Как мы можем получить объект элемента Html из нашей build
функции?
Моей непосредственной реакцией было подумать о том, чтобы, возможно, предоставить метод получения для класса HtmlBuilder
. Что-то тривиальное, как это:
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
, используя другой класс? Я понимаю махинации обычного оператора перегрузки. Для меня имеет смысл перегрузить ()
, []
или =
, но я не понимаю, как и почему этот случай работает. Нет даже объявленного типа возврата; компилятор просто предполагает, что он должен возвращать текущий тип класса?
Спасибо, что уделили время всем.