问题
假设我们实现了一个string
表示,嗯,字符串的类。然后我们想要添加一个operator+
连接两个string
s 的 which ,并决定通过表达式模板来实现它以避免在做str1 + str2 + ... + strN
.
运算符将如下所示:
stringbuilder<string, string> operator+(const string &a, const string &b)
stringbuilder
是一个模板类,它又重载operator+
并具有隐式string
转换运算符。几乎是标准的教科书练习:
template<class T, class U> class stringbuilder;
template<> class stringbuilder<string, string> {
stringbuilder(const string &a, const string &b) : a(a), b(b) {};
const string &a;
const string &b;
operator string() const;
// ...
}
// recursive case similar,
// building a stringbuilder<stringbuilder<...>, string>
只要有人这样做,上述实现就可以完美地工作
string result = str1 + str2 + ... + strN;
但是,它有一个微妙的错误。将结果分配给正确类型的变量将使该变量保存对构成表达式的所有字符串的引用。这意味着,例如,更改其中一个字符串将更改结果:
void print(string);
string str1 = "foo";
string str2 = "bar";
right_type result = str1 + str2;
str1 = "fie";
print(result);
这将打印fiebar,因为 str1 引用存储在表达式模板中。情况变得更糟:
string f();
right_type result = str1 + f();
print(result); // kaboom
现在表达式模板将包含对已破坏值的引用,从而立即使您的程序崩溃。
现在那是什么right_type
?它当然是stringbuilder<stringbuilder<...>, string>
,即表达式模板魔术为我们生成的类型。
现在为什么要使用这样的隐藏类型?事实上,人们并没有明确地使用它——但是 C++11 的 auto 可以!
auto result = str1 + str2 + ... + strN; // guess what's going on here?
问题
底线是:似乎一旦尝试存储表达式模板本身,这种实现表达式模板的方式(通过存储廉价引用而不是复制值或使用共享指针)就会被破坏。
因此,我非常喜欢一种检测我是在构建 rvalue 还是 lvalue 的方法,并根据是构建 rvalue(保留引用)还是构建 lvalue(制作副本)提供表达式模板的不同实现)。
是否有既定的设计模式来处理这种情况?
在我的研究过程中,我唯一能弄清楚的是
可以根据
this
是左值还是右值来重载成员函数,即class C { void f() &; void f() &&; // called on temporaries }
但是,似乎我也不能在构造函数上这样做。
在 C++ 中,不能真正做到“类型重载”,即提供同一类型的多个实现,这取决于类型将如何使用(创建为左值或右值的实例)。