智能指针数组的正确初始化

5
对于这种情况:
    class A
    {
        //implementation
    };

    class B
    {
     public:
         B();
        ~B();
     private:
         std::vector<std::shared_ptr<A>> _innerArray;
    };

我应该在 B() 中做什么来创建一个有效状态的对象?我需要手动为数组中的每个 A 对象调用默认构造函数吗?在 ~B() 中需要特别处理吗?如果 B 类是一个糟糕设计的例子,那么请随意提出如何改进它。谢谢。
编辑: 所以,这里是我真正需要的内容的方案。 实际值仅存储在 A 数组中,所有其他对象都用于存储连接。 最简单的示例 - A = 点,B = 通过选定点的线(或曲线),C =由线描述的平面。希望这使问题更加明确。

1
健全性检查:您是否需要在这里使用指针?如果需要,它应该是共享指针吗? - Konrad Rudolph
@ПавелОганесян 如果指向的对象的生命周期要被延长,且它们包含在您类的实例中,则可以使用shared_ptr。否则,您可以保留原始指针。 - juanchopanza
你应该考虑将“somewhere else”作为参数传递给B的构造函数,并使用std::vector迭代器版本的构造函数来初始化_innerArray,请参见下面我的回答。如果B不应该拥有这些对象,可以考虑使用弱指针而不是共享指针。我不认为使用裸指针有任何理由。 - JohnB
@KonradRudolph 另一种选择是使用 weak_ptr,它是非拥有的但可以防止 B 实例具有无效的原始指针。这将是更好的方法,除非可以 保证 B 实例的生命周期始终短于其包含的任何 A 实例的生命周期。 - Rook
我在问题中添加了一个方案以使其更加清晰。感谢关注。 - Pavel Oganesyan
显示剩余4条评论
5个回答

5
要创建一个处于有效状态的B对象,您无需做任何其他事情。您甚至不必为B声明和实现构造函数和析构函数。B的成员变量std::vector<std::shared_ptr<A>>将在B的构造函数中默认初始化,这意味着容器中还没有任何元素。由于std::vectorstd::shared_ptr的析构函数,它也将被正确删除在~B中。
另一方面,如果您想要初始化它(例如3个值),则可以在B的构造函数初始化列表中使用std::vectorstd::initializer_list构造函数。例如:
class B
{
 public:
     B(): _innerArray{ std::make_shared<A>(),
                       std::make_shared<A>(),
                       std::make_shared<A>() } {}
    ~B() {}
 private:
     std::vector<std::shared_ptr<A>> _innerArray;
};

请记住,std::make_shared使用完美转发,因此您将A的构造函数参数作为函数参数而不是类对象本身传递。

回答您对设计的担忧,我想鼓励您首先考虑在决定共享成员之前,vector中成员的独占所有权。

class B
{
 public:
     B();
    ~B();
 private:
     std::vector<std::unique_ptr<A>> _innerArray;
};

上述实现在许多方面都更有效。首先,它使您的设计更清晰,可以知道谁负责 A 的生命周期。接下来,std::unique_ptr 更快,因为它不需要线程安全的引用计数。最后但并非最不重要的是,与常规 C 指针相比,它不会产生任何额外的内存成本,而 std::shared_ptr 可能需要几十字节(24-48)来存储共享状态数据,这在操作小类时效率极低。这就是为什么我总是将 std::unique_ptr 作为我的首选智能指针,并且只有在真正需要时才会回退到 std::shared_ptr
编辑:
回答您的编辑,我会创建三个类的容器 ABC。根据您是否需要它们具有多态性,我会像这样存储值(非多态类型):
std::deque<A> as;
std::deque<B> bs;
std::deque<C> cs;

或(多态类型):
std::vector<std::unique_ptr<A>> as;
std::vector<std::unique_ptr<B>> bs;
std::vector<std::unique_ptr<C>> cs;

按照顺序排列(as必须比bs活得更久,而bs必须比cs活得更久)。然后在B类中只需要有std :: vector<A *>,而在C类中只需要有std :: vector<B *>,不需要使用任何智能指针。
希望这可以帮到您。 编辑: 在第一个案例中将std :: vector更改为std :: deque,这允许对容器元素的引用/指针通过push_back()在容器扩展时幸存下来。但是,它们不能在擦除元素、排序或其他操作中幸存下来。

2
如果您按照这种方式操作,向量的大小为零,即内容被正确初始化。如果向量大小为正(例如,在向量上调用resize之后),则每个元素都将被正确初始化。由于元素是shared_ptr,因此将调用shared_ptr的默认构造函数,这意味着您最终会得到一个空指针的向量。
如果您想从另一个容器中复制内容,请使用向量构造函数的迭代器版本:
B (SomeContainerTypeContainingSharedPointers container)
: _innerArray (container.begin (), container.end () ) {
}

如果你不想从容器中初始化向量,而是从其他地方初始化(例如,即时创建对象),那么请自己编写输入迭代器类型(即一种“工厂迭代器”)。


1
默认情况下,std::shared_ptr<A> 将使用 NULL 填充内部指针。要创建智能指针,请使用 std::make_shared
_innerArray.push_back(std::make_shared<A>(/*constructor params here*/));

但在你的例子中,向量是空的。


1

向量为空,因此在默认构造函数中不需要执行任何特殊操作。在B()中也不需要执行任何操作。当向量的析构函数被调用时,shared_ptrs的引用计数将自动减少。


0

默认构造函数已经完成了所有必要的工作。你甚至可以不写B()而不会有任何损失。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接