【问题标题】:C++ : Base type member variables as References, or PointerC++:作为引用或指针的基本类型成员变量
【发布时间】:2012-08-29 09:18:08
【问题描述】:

我想知道在 C++ 中解决设计困境的最佳方法可能是什么...

我有一个类,其中包含另一个类的Base 类型的成员变量,并且创建的真实对象创建为DerivedBase

类不需要修改这些变量,它只是使用它们。其他人正在创建这些变量。这些Derived 类还需要转到我类中的容器(std::vector、QList 等)类,因此它们应该执行正确的复制构造和分配。

所以,我想知道什么是最好的:

  1. 将成员变量创建为Base*,让我们管理它们以及它们使用的内存。这会导致典型的内存泄漏问题...有人在不再使用对象时忘记删除它。
  2. 将成员变量创建为Base&,让我们祈祷它们在超出范围时不会消失。

【问题讨论】:

  • 做到这一点(以及其他一切)的最佳方法是在分配和释放变量时始终小心。
  • "Someone just forgets to delete the object" -- 不,如果“we”(这个类)在管理内存,那么“someone”不会忘记删除对象,我们忘记删除对象。永远不应该有“记住”删除某些东西的问题——每当你分配一个对象时,你决定谁负责释放它。该类/函数/任何确保它被释放,并且您编写代码以立即执行此操作。几乎总是将其直接放入智能指针中。不要一开始就抱有“有人必须释放它”的模糊想法,这样你就不会不释放它。

标签: c++ inheritance class-members


【解决方案1】:

拥有引用成员变量总是一个糟糕的选择,因为编译器生成的赋值和移动赋值会做错事,或者不是人们所期望的。

对成员变量坚持使用指针或智能指针。

【讨论】:

  • 我原以为这些根本不是为你生成的——在 VC 2010 中,我收到了警告,但没有错误。 g++ 4.7 没有给我任何警告,除非我尝试使用赋值运算符:错误:使用已删除的函数 'ref_test& ref_test::operator=(const ref_test&)'
  • 刚刚尝试使用 gcc-4.7.1,它确实在 C+98 和 C++11 模式下都会产生错误。如果我没记错的话,过去是不会的。
【解决方案2】:

@hansmaad 是对的,如果您在控制对象的生命周期时遇到问题,您应该与创建或管理它的人共享其所有权。
您有 2 个选择:
1) boost::shared_ptrstd::tr1::shared_ptr
您可以轻松地将此类用于任何类型Base,而无需更改Base,但是如果您在多线程环境中工作,则很难实现shared_ptr 的线程安全,并且不要忘记您是否将对象创建为使用此类之一进行共享,您不应直接管理对象的生命周期,并且从原始指针创建新的共享对象是不合法的,您应该始终复制构造共享对象。例如:

boost::shared_ptr<Base> sharedObject( new Drived() );
boost::shared_ptr<Base> validCopy( sharedObject );    // Ok share ownership
Base* p = sharedObject.get();
boost::shared_ptr<Base> invalidCopy( p );    // Error, can't create new shared_ptr from raw pointer

2) boost::intrusive_ptr
您可以轻松地使其线程安全,并且可以将其作为原始指针或智能指针传递,因为它可以从原始指针构造,因为引用计数是在类中实现的,但是您应该更改类的定义并添加引用计数机制

【讨论】:

    【解决方案3】:

    我会为您的向量(即vector&lt;Base *&gt;,而不是vector&lt;Base&gt;)和您的容器类使用指针,原因如下:

    • 如果将派生对象存储在向量中,则该向量可能会重新调整大小,这会导致所有对象“移动”到内存中的新位置。这将使所有未完成的指针和引用无效
    • 如果您的容器包含引用,您将无法像包含指针那样轻松地复制它,因为引用只能在定义时绑定(因此在构造函数中通过MyClass::MyClass(int &amp;a) : memberA(a) {},如果有记忆的话)李>
    • 可以根据需要通过其他方式(例如设置方法)更改指针,并且可以在缺少信息的情况下设置为 null

    就所有权而言,jrok 是第一个说的:shared_ptr&lt;&gt; 是你的朋友。不要重新发明轮子,只需使用标准库为您简化事情。在这种情况下,您唯一需要担心的是循环指针(即对象指向自身,因此始终存在有效指针)。

    【讨论】:

      【解决方案4】:

      引用成员变量首先要考虑的是你的类(不是Derived,将有一个数据成员是指针或引用Base 的类)是否需要值语义(这是另一个换句话说,“正确地复制和分配”)。

      如果是这样,那么引用成员变量或多或少马上就不可能了,因为它们不能被重新安置。在一些奇怪的情况下,你仍然可以使用它们,但你不妨假设你不会,并使用指针。

      引用数据成员有时对具有“实体语义”的类型很有用(也就是说,它们根本不赋值,可能复制也可能不复制),但它们仍然不会给您带来很多好处。他们还可能会引诱您编写带有const Base&amp; 参数的构造函数并将其存储在引用数据成员[*] 中。

      谁拥有对象(并负责释放它)完全独立于您使用的是指针还是引用。可能有一个一般约定不要对你拥有的东西使用引用(并且应该是一个约定不要对你拥有的东西使用原始指针,你应该选择或编写一个合适的智能指针。智能指针类可以保存一个原始指针)。但这只是惯例。当且仅当你有一个指针时,你不应该假设你管理内存。

      总结:使用指针,然后单独决定如何管理内存。

      [*] 这是一个错误,因为最终有人会在初始化程序中意外使用临时对象,然后您的类的实例及其引用数据成员将比临时对象寿命更长。出于这个原因,在返回后存储引用以供使用的事物不应采用const &amp; 参数,即使它们不修改对象也是如此。他们可以改用const *。在 C++11 中,如果还有右值引用重载,我想它们可能没问题,以防止为临时选择 const&amp; 重载,但这不是我尝试过的东西。

      【讨论】:

        【解决方案5】:

        您应该考虑所有权。谁拥有这些对象?如果这个问题没有明确的答案,您应该使用std::shared_ptr&lt;Base&gt;(共享所有权)。如果有一个类拥有该对象而所有其他类只使用它们,您可以使用std::unique_ptr&lt;Base&gt;,一个像boost::ptr_vector 这样的指针容器,或者如果没有多态性,它只拥有具体实例的类。在所有其他类中,您可以对对象使用普通指针(首选作为类成员)或引用(首选作为参数,如果不允许 null)。

        案例 1 - 共享所有权

        class IWorkOnBaseObjects
        {
            std::vector<std::shared_ptr<Base>> mySubset;
        };
        
        class MeToo
        {
            std::shared_ptr<Base> iNeedThisOne;
        };
        

        案例 2

        class HomeOfBaseObjects
        {
            std::vector<std::uniqe_ptr<Base>> baseObjects;
        };
        
        class IWorkOnBaseObjects
        {
            std::vector<Base*> mySubset;
        };
        

        案例 3

        class A : public Base{};
        class B : public Base{};
        
        class HomeOfAObjects
        {
            std::vector<A> aObjects;
        };
        
        class HomeOfBObjects
        {
            std::vector<B> bObjects;
        };
        
        class INeedABaseObject
        {
            Base* thisOne;
        };
        

        【讨论】:

        • “如果你不能回答这个问题,你应该……”放下 C++,慢慢退开,等你能回答的时候再回来;-) 共享所有权不应该是默认“我不知道”的位置,它应该是对问题的肯定回答。否则你最终会遇到麻烦,你会想出一些共享和弱指针的临时混合来避免引用循环。然后你就会忘记这一点并陷入更多麻烦。
        • @SteveJessop 有(罕见的)没有明确答案的情况。我的意思不是像“啊不知道..让我们拿一个 shared_ptr”
        • 是的,我想你不是那个意思,但我担心有人会那样读!我评论中的“你”的真正意思是“你是这样做的读者”,而不是“你,hansmaad”:-)
        猜你喜欢
        • 1970-01-01
        • 2012-03-22
        • 1970-01-01
        • 2011-04-21
        • 2018-03-24
        • 1970-01-01
        • 2015-02-04
        • 2014-01-24
        • 2016-10-10
        相关资源
        最近更新 更多