【问题标题】:Static members being destroyed while there are still outstanding instances?静态成员在仍有未完成的实例时被销毁?
【发布时间】:2011-05-10 06:07:03
【问题描述】:

我需要从析构函数中访问一个静态数据成员,但在程序退出时似乎不能保证它仍然存在!出于某种原因,静态成员正在被销毁,而该类仍有未完成的实例。这很奇怪,因为我以前从未听过“永远不要从析构函数访问静态成员”的建议,但我想如果它存在的话我会知道这样的限制。

我举个具体的例子:

class MyClass {
    public:
        ~MyClass() { m_instances.erase(m_name); }

    private:
        long m_name;
        static std::map<long, MyClass*> m_instances;
};

在另一堂课中,我尝试了以下令人讨厌的 hack,它似乎有效,但当我考虑它时,我认为它根本不是一个真正的解决方案。

class MyClass {
    friend class Switch;

    public:
        ~MyClass() { if (m_alive) m_instances.erase(m_name); }

    private:

        static bool m_alive;
        class Switch {
            ~Switch() { MyClass::m_alive = false; }
        };
        static Switch m_switch;

        long m_name;
        static std::map<long, MyClass*> m_instances;
};

如果 MyClass 的实例在 m_instances 之后 m_switch 之前被销毁怎么办?即使 m_switch 先死,布尔值 m_alive 也可能已被“破坏”,因此可能被覆盖为“真”(我知道不太可能)。

那么谁能提供更好的解决方案?我希望我在这里遗漏了一些非常明显的东西。

【问题讨论】:

  • 对我来说似乎很奇怪。你遇到过什么编译器/平台?
  • 是什么在程序退出时保持MyClass 的实例以使其析构函数正在运行?
  • 不知是不是类似这样的情况:parashift.com/c++-faq-lite/ctors.html#faq-10.16
  • 你在说什么m_switch?上面的代码中没有m_switch
  • 当然你可以从析构函数中访问全局变量和静态变量。

标签: c++ segmentation-fault destructor static-members


【解决方案1】:

这显然是静态销毁顺序的问题。我会推荐如下内容:

class MyClass {
    public:
        MyClass() { add_instance(m_name, this); };
        ~MyClass() { erase_instance(m_name); }

    private:
        long m_name;
        static std::map<long, MyClass*>& get_instance_map();
        static void add_instance(long aName, MyClass* aObj);
        static void erase_instance(long aName);
};

std::map<long, MyClass*>* MyClass::get_instance_map() {
  static std::map<long, MyClass*>* p_inst = new std::map<long, MyClass*>();
  return p_inst;
};

void MyClass::add_instance(long aName, MyClass* aObj) {
  static std::map<long, MyClass*>* p_inst = MyClass::get_instance_map();
  p_inst->insert( std::make_pair(aName, aObj) );
};

void MyClass::erase_instance(long aName) {
  static std::map<long, MyClass*>* p_inst = MyClass::get_instance_map();
  p_inst->erase( aName );
};

如果您需要删除实例映射,这可能是不可能的。否则,只需使用普通的首次使用构造成语。这里的重点是映射是一个堆分配的 std::map 对象,而不是删除它只是意味着它会在操作系统回收 freestore 内存时被刷新掉,这将在每个其他“正常”执行之后发生,比如析构函数调用。

【讨论】:

  • @Mikael:如果只有一个 MyClass 被构造然后立即被销毁 - 但另一个 MyClass 被构造会发生什么?你的地图丢了吗?
  • 哦,是的,这是真的!我怎么会错过。我会修复一个 EDIT。
  • @Mikael:我认为您在函数习语中使用了静态指针,但这应该会“泄漏”它故意创建的实例。
  • 这似乎工作正常。谢谢你。有没有官方方法可以将此问题标记为“已解决”?
  • @Rob:点击投票下方的大勾号,选出你喜欢的答案。
【解决方案2】:

不能保证静态成员仅在同一类的对象的所有实例之后才被销毁。 C++ 没有包含引用计数范例(尽管有shared_ptr)。

在考虑生命周期时,请将您的静态成员视为任何其他静态对象。除了在类的“命名空间”中之外,实际上没有任何东西将它们绑定到它们的包含类(警告:术语不准确)。

所以,如果您的 myClass 实例也是静态创建的,那么您需要考虑它们之间的正常静态对象生命周期规则:

Example #1:

#include <iostream>

struct A {
    A() { std::cout << "*A "; };
   ~A() { std::cout << "~A "; };
};

struct B {
    B() { std::cout << "*B "; };
   ~B() { std::cout << "~B "; };

    static A a;
};

B t;
A B::a;

int main() {}

// Output: *B *A ~A ~B 

如您所见,B 的静态成员在B 的实际实例之前被销毁。

Example #2:

#include <iostream>

struct A {
    A() { std::cout << "*A "; };
   ~A() { std::cout << "~A "; };
};

struct B {
    B() { std::cout << "*B "; };
   ~B() { std::cout << "~B "; };

    static A a;
};

A B::a;
B t;

int main() {}

// Output: *A *B ~B ~A 

这里正好相反。对于您当前的问题,这是一个便宜的解决方案,但我的建议是完全避免静态实例;你只会陷入更多像这样的静态初始化陷阱......可能未来会出现相同的代码!

【讨论】:

  • 这只会发生在全局对象上,并且只会在程序结束时发生。一个对象被封装为另一个命名空间中的静态对象并不会改变它基本上是一个全局对象的事实
  • @drhirsch:我刚刚说了这么多! (不过,您对“全局”的使用是不对的。)
  • 好吧 :-) 但我认为这无关紧要,因为我无法想象 OP 会使用全局 MyClass,可能会在全局 MyClass 之后创建一个静态成员,然后问他为什么访问静态成员时遇到问题...现在我不太确定了 ;-)
  • @drhirsch:我的回答是基于有根据的猜测,即这是问题的原因。 :) 毕竟,如果不是这样,那么这种情况就不应该发生。
【解决方案3】:

如果您在使用静态时遇到此类问题,则一定意味着 MyClass 也具有静态范围,并且您不能设计这样的代码,其中一个静态访问另一个静态。它可能有效,也可能无效,因为您的破坏顺序有问题。

您也完全有可能有另一个全局(静态)导致内存损坏。如果是这种情况,则可能意味着覆盖一个全局变量可能会覆盖位于同一内存空间中的全局变量附近的其他全局变量,即您遇到问题的静态变量已损坏且未被删除。

【讨论】:

    猜你喜欢
    • 2012-09-11
    • 2018-08-13
    • 1970-01-01
    • 2015-08-23
    • 2018-05-02
    • 2017-06-14
    • 1970-01-01
    • 1970-01-01
    • 2019-09-12
    相关资源
    最近更新 更多