8

表达式模板通常用作一种优化技术,以避免创建临时对象。他们推迟构造完整的对象,直到模板用于赋值或初始化。这可用于字符串构建器、线性代数包等。

为了避免昂贵的副本,表达式模板类可以通过引用捕获更大的参数。我将以 QtQStringBuilder为例。

当引用超过表达式模板时,它可以工作:

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这里的 s 是隐式共享的,因此它们的复制很便宜,尽管仍然比持有引用更昂贵。但是,假设参数是std::string:除非必要,否则您绝对不想复制它们。

有没有什么技术可以用来检测一个完整的模板表达式没有立即解析,并且必须复制数据到目前为止只持有一个引用?

注意:我不是在询问表达式模板的任何特定现有实现。我只QStringBuilder用作一个激励的例子。这不是 Qt 问题,也不是特征问题等。标题就是问题,差不多。

4

2 回答 2

2

您不可能可靠地检测到对象的引用何时失效,除非该对象以某种方式指示它将失效。您也无法提前检测是否为您的表达式对象调用任何特定函数,您只能在实际调用它时检测到它已被调用。

如果您的对象确实提供了一种检测破坏的方法,例如,如果它有一些事件系统告诉您有关它的信息,那么您应该能够修改您的表达式对象。不只是持有对原始数据对象的引用,而是持有一个标记的联合。最初,存储指向原始数据对象的指针。当这些数据对象即将被销毁时,复制数据并更新标签。

但请记住,这并不能防止问题以其他方式出现。例如,一个对象可能已被移出,在这种情况下,即使该对象仍然存在,但仍未被破坏,它所保存的数据在任何合理的意义上都不再有意义。

最终,我认为您正在尝试使用技术手段来解决非技术问题:您已经决定(合理地,IMO)在构建表达式时不想复制数据,即使是在数据对象使用 COW。您需要让用户了解该决定的后果,或者修改您的决定。

于 2015-09-14T14:36:17.750 回答
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
于 2015-09-14T15:15:26.537 回答