表达式模板通常用作优化技术,以避免创建临时对象。它们推迟构造完整对象,直到模板用于赋值或初始化。这可以在字符串构建器,线性代数包等中使用。
为了避免昂贵的副本,表达式模板类可以通过引用捕获更大的参数。我将以Qt' QStringBuilder
为例。
当引用超过表达式模板时,它可以工作:
QString foo = QString("A") + QString("B");
^^^^^^^^^^^^^^^^^^^^^^^^^^^
QStringBuilder<QConcatenable<QString>,
QConcatenable<QString>>
表达式模板的转换和解析发生在赋值中。字符串临时工作比作业更长。
唉,一旦推断出表达式模板类型而不是目标类型,我们就会遇到麻烦:
// WORKS
QString foo = []() -> QString { return QString("A") + QString("B"); }();
// FAILS
QString foo = []{ return QString("A") + QString("B"); }();
还有:
auto foo = QString("A") + QString("B");
// foo holds references to strings that don't exist anymore
QString bar = foo; // oops
一种解决方案是让构建器保存对象的副本。由于QString
这里是隐式共享的,因此它们的复制很便宜,尽管比持有引用更昂贵。但是假设参数是std::string
:除非必要,否则你绝对不想复制它们。
是否有任何技术可用于检测完整的模板表达式是否未立即解析并且必须复制数据,因此迄今为止只能引用?
注意:我没有询问任何特定的表达模板的现有实现。我只使用QStringBuilder
作为激励示例。这不是Qt问题,也不是特征问题等。标题就是这个问题。
答案 0 :(得分:2)
除非对象以某种方式表明它将被无效,否则您无法可靠地检测何时对对象的引用失效。您是否也无法事先检测到为表达式对象调用任何特定函数 ,您只能检测到它实际被调用时已被调用。
如果您的对象 提供了检测销毁的方法,例如,如果它有一些事件系统告诉您,那么您应该能够修改表达式对象。不要只是持有对原始数据对象的引用,而是持有标记的联合。最初,存储指向原始数据对象的指针。当这些数据对象即将被破坏时,复制数据并更新标记。
但请记住,这并不能防止问题以其他方式出现。例如,一个对象可能已被移动,在这种情况下,即使对象仍然存在,仍然没有被破坏,它所拥有的数据在任何合理的意义上都不再有意义。
最终,我认为您正在尝试使用技术手段解决非技术问题:您已经(合理地,IMO)决定在构建表达式时不想复制数据,甚至不是当数据对象使用COW时。您需要向用户介绍该决定的后果,或者修改您的决定。
答案 1 :(得分:0)
是否有任何技术可用于检测完整的模板表达式是否未立即解析并且必须复制数据,因此迄今为止只能引用?
这种情况主要发生在您复制Builder
时,因此您可以在复制构造函数中进行管理:
struct Expr
{
explicit Expr(const std::string& s) : s(s) {};
const std::string& s;
};
struct ExprBuilder
{
// deleted
//~~or provide implementation which copy operand (or result)~~ RVO may avoid that fix
ExprBuilder(const ExprBuilder&) = delete;
ExprBuilder& operator = (const ExprBuilder&) = delete;
ExprBuilder(const Expr& lhs, const Expr& rhs) : lhs(lhs), rhs(rhs) {}
operator std::string() const { return lhs.s + rhs.s; }
Expr lhs;
Expr rhs;
};
ExprBuilder operator + (const Expr& lhs, const Expr& rhs)
{
return {lhs, rhs};
}
int main() {
std::string s = Expr("hello") + Expr(" world");
std::cout << s << std::endl;
auto builder = Expr("hello") + Expr(" world"); // Use of copy constructor here
s = builder;
std::cout << s << std::endl;
std::string foo =
[]{ return Expr("A") + Expr("B"); } // Use of copy constructor here
();
}
但是当你使用const引用延长临时生命时它仍然失败
const auto& builder = Expr("hello") + Expr(" world"); // Undetected error here