【问题标题】:Crazy talk (paranoid about initialization)疯狂的谈话(对初始化的偏执)
【发布时间】:2012-08-23 11:46:47
【问题描述】:

我很久以前就知道,唯一可靠的方法是在函数中初始化静态成员。现在,我要做的是开始通过非常量引用返回静态数据,我需要有人阻止我。

function int& dataSlot()
{
    static int dataMember = 0;
    return dataMember;
}

据我所知,这是确保静态成员初始化为零的唯一方法。但是,它会创建像这样的晦涩代码:

dataSlot() = 7; // perfectly normal?

另一种方法是将定义放在翻译单元中,并将内容放在头文件之外。我对此本身没有任何反对意见,但我不知道标准所说的什么时候以及在什么情况下是安全的。

我最不想做的事情是意外访问未初始化的数据并失去对我的程序的控制。

【问题讨论】:

  • 为什么需要返回非常量引用?
  • @Andrey 我有一个用例,我实际上希望能够修改静态值,但我也希望它具有特定的默认值。
  • dataSlot()=7 这样的代码是完全正常的。
  • 如果您觉得这个问题超出了您的想象,我很抱歉@Mankarse
  • @JohnLeidegren:我没有看到任何问号,我也不觉得dataSlot() = 7 晦涩难懂。我当然认为不可能对“我需要有人阻止我”给出明确的事实答案。

标签: c++


【解决方案1】:

(通常注意不要随意使用全局变量...)只需在全局范围内声明变量即可。它保证在任何代码运行之前被零初始化。

当涉及到具有非平凡构造函数的类型时,你必须更加狡猾,但 int 可以作为全局变量正常工作。

【讨论】:

  • 与全局变量的区别在于,单个实例是在函数调用时创建的,所以功能不一样(特别是可以从构造函数中抛出异常时)。
  • @BЈовић:int 的“构造函数”不会抛出。
  • 好的,对。我在想一个更一般的情况(这东西是如何真正使用的)
  • 好的,我会检查一下,我只处理原始类型(和指针),所以这可能工作得很好。我希望保持简单。
  • 全局变量和静态成员变量有区别吗?
【解决方案2】:

返回一个非常量引用本身是无害的,例如vector::at()vector::iterator::operator* 所做的。

如果你不喜欢 dataSlot() = 7; 的语法,你可以定义:

void setglobal(int i) {
    dataSlot() = i;
}
int getglobal() {
    return dataSlot();
}

或者你可以定义:

int *dataSlot() {
    static int dataMember = 0;
    return &dataMember;
}

*dataSlot() = 7; // better than dataSlot() = 7?
std::cout << *dataSlot(); // worse than std::cout << dataSlot()?

如果您想让某人阻止您,他们需要更多信息才能提出替代您使用可变全局状态的方法!

【讨论】:

  • 我确实考虑过隐藏它作为非常量引用返回的事实,但我认为我可以通过在翻译单元中简单地定义它来解决问题,但我不确定初始化顺序。
  • 如果我有一个带有int MyClass::dataSlot = 0 的 cpp 文件,我是否可以访问 dataSlot 并且它不为零?
  • 仅当您将其修改为其他值时。
【解决方案3】:

它被称为Meyers singletor,它几乎是绝对安全的。

你必须注意对象是在调用函数 dataSlot() 时创建的,但是当程序存在时它会被销毁(当全局变量被破坏时),因此你必须特别小心。在析构函数中使用这个函数特别危险,可能会导致随机崩溃。

【讨论】:

  • 几乎正确。就我而言,我正在考虑投反对票(不投反对票,因为链接很好)。对象在函数第一次被调用时被构造。运行时确保您无需处理任何事情。它在全局销毁期间被销毁,与构造函数完成的顺序相反。运行时会处理这个问题(除非应用程序被终止或终止()),通常是通过底层 C 库的 atexit 注册析构函数。
  • 它不是“完全安全的”:它可以在其他静态对象之前被销毁,然后可以尝试从它们的析构函数中访问它。在这种情况下,这可能会被忽视,因为它有一个微不足道的析构函数;但更一般地说,这可能导致未定义的行为。
  • @Mike:破坏的顺序是构造函数完成的相反顺序,不是吗?因此,如果您希望您的对象在其析构函数中引用该事物,正确的做法是在其构造函数中也引用它,从而确保全局在您的对象之前构造并因此在之后销毁。我需要这种东西已经有一段时间了,不过,我有点同意,我不确定这一事实意味着它不能真正被称为“完全”安全。
  • @SteveJessop:是的,我想你可以这样做;但它很容易出错,因此仍然不是“完全安全”。
  • 哦,我应该提到如果我的对象在其析构函数中引用它具有动态生命周期,那么在构造函数中引用没有帮助。这比 C++ 中的许多东西更容易出错。无论如何,“安全”是相对的:如果“出错的机会”意味着“不完全安全”,那么 C++ 的完全安全子集可能是空的;-)
【解决方案4】:

我很久以前就知道,唯一可靠的方法是在函数中初始化静态成员。

不,不是。该标准保证:

  1. 所有具有静态存储的对象(块和文件或类静态范围)具有普通构造函数,在任何代码运行之前都会被初始化。程序的任何代码。
  2. 在调用main 函数之前,所有具有文件/全局/类静态范围和非平凡构造的对象都将被初始化。可以保证,如果对象 A 和 B 在同一个翻译单元中定义并且 A 在 B 之前定义,则 A 在 B 之前初始化。但是,在不同翻译单元中定义的对象的构造顺序是未指定的,并且在编译之间通常会有所不同。
  3. 当第一次达到声明时,初始化任何块静态对象。由于 C++03 标准不支持线程,this is NOT thread-safe!
  4. main() 函数退出或应用程序使用exit() 系统调用。

这两种方法都不是在所有情况下都可用且可靠!

现在,我要做的是开始通过非常量引用返回静态数据,我需要有人阻止我。

没有人会阻止你。这是合法且完全合理的事情。但请确保您不会陷入线程陷阱。

例如任何合理的 C++ 单元测试库都会自动注册所有测试用例。它通过以下方式实现:

std::vector<TestCase *> &testCaseList() {
    static std::vector<TestCase *> test_cases;
    return test_cases;
}

TestCase::TestCase() {
    ...
    testCaseList().push_back(this);
}

因为这是仅有的两种方法之一。另一个是:

TestCase *firstTest = NULL;

class TestCase {
    ...
    TestCase *nextTest;
}

TestCase::TestCase() {
    ...
    nextTest = firstTest;
    firstTest = this;
}

这次使用firstTest 具有平凡构造函数的事实,因此在任何具有非平凡构造的TestCases 之前初始化。

dataSlot() = 7; // 完全正常?

是的。但如果你真的想要,你可以这样做:

  1. 老C的东西

    #define dataSlot _dataSlot()
    

    在某种程度上,errno“变量”通常是定义的,

  2. 或者你可以将它包装在一个结构中

    class dataSlot {
        Type &getSlot() {
            static Type slot;
            return slot;
        }
        operator const Type &() { return getSlot(); }
        operator=(Type &newValue) { getSlot() = newValue; }
    };
    

    (这里的缺点是,如果你尝试直接在 dataSlot 上调用 Type 的方法,编译器不会查找它们;这就是它需要 operator= 的原因)

【讨论】:

    【解决方案5】:

    你可以为自己创建 2 个函数,dataslot() 和 set_dataslot(),它们是实际数据槽的包装器,有点像这样:

    int &_dataslot() { static int val = 0; return val; }
    int dataslot() { return _dataslot(); }
    void set_dataslot(int n) { _dataslot() = n; }
    

    您可能不想在标头中内联那么多内容,但我发现如果您尝试这种事情,一些 C++ 实现的效果会相当糟糕。

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 2012-05-03
      • 1970-01-01
      • 2011-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      • 1970-01-01
      相关资源
      最近更新 更多