避免使用表达式模板#define

发布于 2024-11-04 20:27:06 字数 653 浏览 0 评论 0原文

对于以下代码,“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是错误的并且按值存储不会影响性能。

With the following code, "hello2" is not displayed as the temporary string created on Line 3 dies before Line 4 is executed. Using a #define as on Line 1 avoids this issue, but is there a way to avoid this issue without using #define? (C++11 code is okay)

#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
}

Clarification:

Note that I believe Boost uBLAS stores references, this is why I don't want to store a copy. If you suggest that I store by value, please explain why Boost uBLAS is wrong and storing by value will not affect performance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

柳若烟 2024-11-11 20:27:06

按引用存储的表达式模板通常是为了性能而这样做,但需要注意的是,它们只能用作临时

模板取自 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

Expression templates that do store by reference typically do so for performance, but with the caveat they only be used as temporaries

Taken from the documentation of Boost.Proto (which can be used to create expression templates):

Note An astute reader will notice that the object y defined above will be left holding a dangling reference to a temporary int. In the sorts of high-performance applications Proto addresses, it is typical to build and evaluate an expression tree before any temporary objects go out of scope, so this dangling reference situation often doesn't arise, but it is certainly something to be aware of. Proto provides utilities for deep-copying expression trees so they can be passed around as value types without concern for dangling references.

In your initial example this means that you should do:

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

That way the C temporary never outlives the std::string temporary. Alternatively you could make s a non reference member as others pointed out.

Since you mention C++11, in the future I expect expression trees to store by value, using move semantics to avoid expensive copying and wrappers like std::reference_wrapper to still give the option of storing by reference. This would play nicely with auto.

A possible C++11 version of your code:

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
};

This would mean that code like C("hello").get() would only allocate memory once, but still play nice with

std::string clvalue("hello");
auto c = C(clvalue);
std::cout << c.get() << '\n'; // no problem here
同尘 2024-11-11 20:27:06

但是有没有办法在不使用#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 stills 指向它(死亡对象)。

but is there a way to avoid this issue without using #define?

Yes.

Define your class as: (don't store the reference)

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

Store the copy!

Demo : http://www.ideone.com/GpSa2


The problem with your code is that std::string("hello2") creates a temporary, and it remains alive as long as you're in the constructor of C, and after that the temporary is destroyed but your object x2.s stills points to it (the dead object).

ぇ气 2024-11-11 20:27:06

编辑后:

通过引用存储有时危险且容易出错。仅当您 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; }
};

After your edit:

Storing by reference is dangerous and error prone sometimes. You should do it only when you are 100% sure that the variable reference will never go out of scope until its death.

C++ string is very optimized. Until you change a string value, all will refer to the same string only. To test it, you can overload operator new (size_t) and put a debug statement. For multiple copies of same string, you will see that the memory allocation will happen only once.

You class definition should not be storing by reference, but by value as,

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

If this question is meant for general sense (not specific to string) then the best way is to use dynamic allocation.

class C {
  MyClass *p;
  C() : p (new MyClass()) {}  // just an example, can be allocated from outside also
 ~C() { delete p; }
};
终止放荡 2024-11-11 20:27:06

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

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

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

Without looking at BLAS, expression templates typically make heavy use of temporary objects of types you aren't supposed to even know exists. If Boost is storing references like this within theirs, then they would suffer the same problem you see here. But as long as those temporary objects remain temporary, and the user doesnt store them for later, everything is fine because the temporaries they reference remain alive for as long as the temporary objects do. The trick is you perform a deep copy when the intermediate object is turned into the final object that the user stores. You've skipped this last step here.

In short, it's a dangerous move, which is perfectly safe as long as the user of your library doesn't do anything foolish. I wouldn't recommend making use of it unless you have a clear need, and you're well aware of the consequences. And even then, there might be a better alternative, I've never worked with expression templates in any serious capacity.

As an aside, since you tagged this C++0x, auto x = a + b; seems like it would be one of those "foolish" things users of your code can do to make your optimization dangerous.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文