【问题标题】:Pointers and references to stack-allocated object指向堆栈分配对象的指针和引用
【发布时间】:2014-03-14 14:10:21
【问题描述】:

假设我有以下代码(它可能过于简单,但我认为这就足够了):

struct BigObject {
   A a;
   B b;
};

struct A { ... };

struct B {
    A const* a_ptr;
    MyOtherClass someMethodUsingA() { ... }
}

class BigObjectBuilder {
    BigObject build() {
        BigObject o;
        o.a = buildA();
        o.b.a_ptr = &(o.a);

        return o;
    }
}

问题在于 &(o.a) 可能会指向稍后的任何内容,因为 o 的地址在返回时可能已更改(调用移动或复制构造函数)。现在我发现大多数时候 &(o.a) 是相同的,所以调用 *(o.b.a_ptr) 不会导致段错误。 (我认为这是由于 RVO,因为那时 o 没有移动)。无论如何,只要我不确定 o 的地址没有改变,这段代码就不正确。

一个明显的解决方案是要求动态分配我的 BigObject:

auto o_ptr = make_unique<BigObject>();

这个解决方案还不错(没有泄漏,并且解决了前面的问题。),但是我仍然觉得它不够优雅:我不需要动态分配,我只需要为我的 BigObject 提供一个固定地址。这将是我认为的 Java 做事方式。 另一种解决方案是在 B 中使用副本,但是 o.a 中的所有更改都不会影响 o.b.a,我不希望这样。

第三种解决方案是在 B 中没有 A 的副本、ptr 或引用,并在方法 B::someMethodUsingA() 的参数中传递 A。但同样,在调用此函数时查找要传递的参数可能很乏味。有时,B 类有一个指向 A 类的指针感觉更自然。

现在我发现这个问题在我的代码中一次又一次地发生:我想构建一个复杂的对象,子对象相互引用。根本问题是对象不是“就地”创建的,它可能会在构建过程中稍后移动(但不是在构建完所有内容之后)。

他们是否有任何已知的模式可以适用于此?我想这是一个很常见的问题,如果不是,那一定是因为我没有以正确的方式构建我的系统......

有没有办法确保某种 RVO ?我的意思是,由标准确保,而不是由编译器确保。类似于编译器警告说“BigObject”不能移动到不同的地址”

我还想过删除移动和复制 ctor 和赋值运算符。但是我无法按值返回对象,即使实际上没有任何移动......

欢迎分享任何对你有用的东西!

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    我不明白为什么你需要一个结构对象及其在结构BigObject 中的指针。 你可以这样设计结构:

    struct A { ... };
    struct BigObject {
       A a;
       MyOtherClass someMethodUsingA() { /* Here you can use the variable a. */ }
    };
    

    如果你还想按你的方式编码,你可以为struct BigObject添加copy-construct和assign-construct函数,像这样:

    struct BigObject 
    {
         A a;
         B b;
    
         // copy-construct function
         BigObject(const BigObject& old) 
         {
             a = old.a;
             b = old.b;
             b.a_ptr = &a; // pay attetion
         }  
    
         // assign construct function
         BigObject& operator=(const BigObject& old)
         {
              if (this == &old) // avoid self-assignment
              {
                  return *this;
              }
    
              a = old.a;
              b = old.b;
              b.a_ptr = &a; 
    
              return *this;
         }
    };
    

    【讨论】:

    • 好的,我设法使用了接近你答案的东西。我添加了一个移动 ctor/assignement 而不是复制。目前代码不是那么干净(因为几个不相关的问题),但经过一番思考后应该这样做。关于需要添加一个B类:在这个玩具示例中,不多,但在实际代码中,B和BigObject都有很多方法,我想分开责任
    • 一个评论:有没有人发现自己处于这种情况(有ptr到移动的对象)?我在我的代码中一次又一次地找到它,但如果没有其他人这样做,我可能只是在构建对象的方式上出错了。
    【解决方案2】:

    给B的函数添加一个A引用参数,BigObject调用函数时,可以传递它的A。A和B应该是私有的。如有必要,将包装函数添加到 BigObject,重定向到对 A 和 B 的调用,并根据需要传递 A 参数。

    不要编写需要 RVO 才能工作的代码。

    【讨论】:

    • 关于 A 和 B 是私有的:别担心,它们在我的实际代码中是私有的。现在正如我解释的那样,使用您的解决方案对我来说似乎很乏味。在这种情况下,问题不大,但是假设 As 和 Bs 是 BigObject 的 sub-sub-sub 成员,那么问题就来了:你必须在每个 sub-member 中添加 getA(),你必须有一个对包含的引用对象...
    • @BérengerBerthoul 听起来您的设计有问题。您的类按层次结构布局,但子对象需要上下很多才能相互通信。
    • 好吧,如果我不使用这种将对象作为函数参数传递的解决方案,我就不会有这个问题。如果层次结构中的一个对象持有一个指向层次结构中的其他对象的指针,但位于完全不同的位置,则无需在层次结构中上下移动
    猜你喜欢
    • 2015-06-07
    • 2020-07-26
    • 2012-09-16
    • 2017-12-06
    • 2013-08-19
    • 2012-02-05
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多