【问题标题】:std::vector not retaining pushed back objects instantiated in the implementationstd::vector 不保留在实现中实例化的推回对象
【发布时间】:2016-07-08 03:44:05
【问题描述】:

我在 plane.h 中声明了一个向量和一个对象:

extern Plane Default;
extern std::vector<Plane *>universe;

它们在plane.cpp中定义:

Plane Default("Default");
std::vector<Plane *>universe;

平面构造函数:

Plane::Plane(const std::string &label) {
    /* check universe to ensure uniqueness */
    std::cout << this << std:endl; //DEBUG CHECK to see what I push_back

    universe.push_back(this); //ACTION to keep track of the planes

    std::cout << universe.back() << std::endl; //DEBUG CHECK to ensure that it was stored correctly
}

输出确认平面确实存储在向量中。

主要:

if(universe.empty()) cout << "EMPTY UNIVERSE" << endl;

表示向量没有保留值。我希望将默认值(在 plane.cpp 中定义)存储在 Universe 中。

Demo

但是,当我从 main 实例化平面时,宇宙会保留这些值

我猜它与本地副本、范围和按值传递有关,但我无法找到一种方法来实例化实现内部的默认平面,以便将其地址保留在向量中。

我也尝试过从堆中声明向量,如下所示:

extern std::vector<Plane *> *universe;

并定义如下:

std::vector<Plane *> *universe = new std::vector<Plane *>;

它只是使程序崩溃。 我在 Code:Blocks 16.01 和 -std=C++11 中的 64 位 Vista 机器上使用 MinGW32 4.9

【问题讨论】:

  • 你能把这一切归结为minimal reproducible example吗?
  • 全球初始化订单惨败...
  • 我的猜测是您通过值传递了universe,将Plane 放入副本中,销毁副本,然后检查原件是否包含Plane,但事实并非如此。但是没有看到代码,这只是猜测。

标签: c++ vector scope implementation


【解决方案1】:

你打了全局初始化命令惨败:

Plane Default("Default");
std::vector<Plane *>universe;

Defaultuniverse有依赖,但是在构造Default时,universe还没有被构造出来。

这就是为什么您通常应该避免使用全局。

在您的情况下,一种解决方法是更改​​顺序,假设存在相同的翻译单元:

std::vector<Plane *>universe;
Plane Default("Default");

【讨论】:

  • 我刚刚确认这确实解决了问题:coliru.stacked-crooked.com/a/ba1d03cb9f19b380.
  • Nitpick: TTBOMK 当你在 不同 源文件中有静态变量时,静态初始化顺序失败——因为程序是否正常工作取决于链接器处理的顺序他们,IOW,它(通常)甚至不受程序员的控制!在这里,虽然程序在没有警告的情况下编译并给出错误的行为肯定很糟糕,但程序员至少可以通过正确的定义顺序来保证正确的程序行为。
  • 这确实是问题所在。您的解决方案就像一个魅力!非常感谢您的有用回复。
【解决方案2】:

answer by Jarod42 应该可以解决您遇到的问题。我将提供一些指导方针来防止此类问题。

一般来说,最好避免使用 extern 声明的变量访问的全局数据。最好使用函数接口提供对全局数据的访问。

你可以改变

extern std::vector<Plane *>universe;

extern std::vector<Plane *>& getUniverse();

并将其实现为:

extern std::vector<Plane *>& getUniverse()
{
   static std::vector<Plane *>universe;
   return universe;
}

有了这个,你可以把Plane的构造函数改成:

 Plane::Plane(const std::string &label) {
    getUniverse().push_back(this);
}

这将消除与全局数据初始化顺序相关的问题。

你也可以改变

extern Plane Default;

extern Plane& getDefaultPlane();

并将其实现为

extern Plane& getDefaultPlane()
{
   static Plane Default("Default");
   return Default;
}

如果必须在初始化时构造默认的Plane,您可以使用以下内容:

// Use a helper struct in an anonymous namespace to initialize
// whatever needs to be initialized.
namespace
{
    struct Initializer
    {
       Initializer();
    };
}

// Make sure that Initializer::Initializer() gets called
// at initialization time.
static Initializer initer;

Initializer::Initializer()
{
   // Trigger construction of the default Plane
   getDefaultPlane();
}

【讨论】:

  • 好建议,但 OP 的问题不涉及多个翻译单元,因此关于外部可见性的部分似乎与这里无关。此外,我之前还没有在您的上一节中看到过这种方法(“如果是默认的 Plane...),我想知道它在一般情况下或在单翻译单元的情况下实现了什么。
  • @j_random_hacker,它为您提供了一种在启动时强制初始化对象的技术,对初始化顺序的控制程度比使用全局数据所能实现的程度更大。
  • 多么有趣!我对 C++(和一般编程)很陌生。我喜欢您的解决方案添加的抽象层。我刚刚实现了你的方式。
  • @EhsanAmini,很高兴我能提供帮助。
【解决方案3】:

如果您对指针的范围有疑问,也许使用 shared_ptr 就足够了?但是,如果您担心指针的持久性可能会被删除或移动并且仍然需要数据,我不知道您的整体设计;智能指针可能是答案。如果任何地方都存在循环问题,请使用 weak_ptr 来打破它。

祝你好运!

【讨论】:

  • 这听起来像是一个扩展评论,而不是一个答案。
  • 是的,显然没有足够的代表发表评论……所以添加任何建议作为答案似乎是合适的。
  • 实际上,正如@Jarod42 和 R Sahu 正确指出的那样,问题在于初始化的顺序。更改顺序解决了这个问题,并添加了一个初始化程序进一步改进了它。但如前所述,我对编程很陌生。我将研究 shared_ptr 和智能指针。谢谢你的提示。
猜你喜欢
  • 1970-01-01
  • 2023-03-15
  • 2016-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多