1

使用以下代码,“hello2”不会显示,因为在第 3 行创建的临时字符串在第 4 行执行之前死亡。在第 1 行使用 #define 可以避免这个问题,但是有没有办法在不使用 #define 的情况下避免这个问题?(C++11 代码没问题)

#include <iostream>
#include <string>

class C
{
public:
  C(const std::string& p_s) : s(p_s) {}
  const std::string& s;
};

int main()
{
  #define x1 C(std::string("hello1")) // Line 1
  std::cout << x1.s << std::endl; // Line 2

  const C& x2 = C(std::string("hello2")); // Line 3
  std::cout << x2.s << std::endl; // Line 4
}

澄清:

请注意,我相信 Boost uBLAS 存储引用,这就是我不想存储副本的原因。如果您建议我按值存储,请解释为什么 Boost uBLAS 是错误的,按值存储不会影响性能。

4

4 回答 4

5

确实通过引用存储的表达式模板通常这样做是为了提高性能,但需要注意的是,它们仅用作临时对象

取自 Boost.Proto的文档(可用于创建表达式模板):

注意精明的读者会注意到上面定义的对象 y 将保留对临时 int 的悬空引用。在 Proto 处理的高性能应用程序中,通常会在任何临时对象超出范围之前构建和评估表达式树,因此通常不会出现这种悬空引用的情况,但肯定需要注意这一点. Proto 提供了用于深度复制表达式树的实用程序,因此它们可以作为值类型传递而无需担心悬空引用。

在您的初始示例中,这意味着您应该执行以下操作:

std::cout << C(std::string("hello2")).s << std::endl;

这样,C临时的永远不会超过std::string临时的。或者,您可以s像其他人指出的那样创建一个非参考成员。

既然您提到了 C++11,我希望将来表达式树按值存储,使用移动语义来避免昂贵的复制和包装器(如 std::reference_wrapper)仍然提供按引用存储的选项。这将与auto.

您的代码可能的 C++11 版本:

class C
{
public:
    explicit
    C(std::string const& s_): s { s_ } {}

    explicit
    C(std::string&& s_): s { std::move(s_) } {}

    std::string const&
    get() const& // notice lvalue *this
    { return s; }

    std::string
    get() && // notice rvalue *this
    { return std::move(s); }

private:
    std::string s; // not const to enable moving
};

这意味着类似的代码C("hello").get()只会分配一次内存,但仍然可以很好地使用

std::string clvalue("hello");
auto c = C(clvalue);
std::cout << c.get() << '\n'; // no problem here
于 2011-05-03T04:03:59.663 回答
4

但是有没有办法在不使用#define 的情况下避免这个问题?

是的。

将您的类定义为:(不要存储参考)

class C
{
public:
  C(const std::string & p_s) : s(p_s) {}
  const std::string s; //store the copy!
};

保存副本!

演示:http ://www.ideone.com/GpSa2


您的代码的问题是std::string("hello2")创建了一个临时对象,只要您在 的构造函数中C,它就会一直存在,之后临时对象被销毁,但您的对象x2.s仍然指向它(死对象)。

于 2011-05-03T03:06:21.410 回答
0

编辑后:

按引用存储有时很危险且容易出错。只有当您 100% 确定变量引用在其死亡之前永远不会超出范围时,您才应该这样做。

C++string非常优化。在您更改字符串值之前,所有都将仅引用相同的字符串。要对其进行测试,您可以重载operator new (size_t)并放置一个调试语句。对于同一字符串的多个副本,您将看到内存分配只会发生一次。

您的类定义不应该按引用存储,而是按值存储,

class C {
  const std::string s;  // s is NOT a reference now
};

如果这个问题是为了一般意义(不是特定于字符串),那么最好的方法是使用动态分配。

class C {
  MyClass *p;
  C() : p (new MyClass()) {}  // just an example, can be allocated from outside also
 ~C() { delete p; }
};
于 2011-05-03T03:08:17.587 回答
0

如果不查看 BLAS,表达式模板通常会大量使用您甚至不应该知道存在的类型的临时对象。如果 Boost 在他们的内部存储这样的引用,那么他们将遇到与您在此处看到的相同的问题。但是只要这些临时对象仍然是临时的,并且用户不存储它们以备后用,一切都很好,因为只要临时对象存在,它们引用的临时对象就会保持活动状态。诀窍是当中间对象转换为用户存储的最终对象时执行深度复制。您在这里跳过了最后一步。

简而言之,这是一个危险的举动,只要你图书馆的用户不做任何愚蠢的事情,这是绝对安全的。除非您有明确的需求,并且您很清楚后果,否则我不建议您使用它。即便如此,可能还有更好的选择,我从未以任何严肃的身份使用过表达式模板。

顺便说一句,既然你标记了这个 C++0x,auto x = a + b;它似乎是你的代码用户可以做的那些“愚蠢”的事情之一,使你的优化变得危险。

于 2011-05-03T03:34:01.527 回答