1

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

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

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

dataSlot() = 7; // perfectly normal?

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

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

4

5 回答 5

6

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

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

于 2012-08-23T11:52:11.557 回答
4

返回一个非常量引用本身是相当无害的,例如它是做什么的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()?

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

于 2012-08-23T11:54:24.783 回答
3

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

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

于 2012-08-23T11:49:41.597 回答
2

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

不,不是。该标准保证:

  1. 所有带有普通构造函数的静态存储(块和文件或类静态范围)的对象都会在任何代码运行之前初始化。程序的任何代码。
  2. 所有具有文件/全局/类静态范围和非平凡构造的对象都在main调用函数之前进行初始化。可以保证,如果对象 A 和 B 在同一个翻译单元中定义并且 A 在 B 之前定义,则 A 在 B 之前初始化。但是,在不同翻译单元中定义的对象的构造顺序是未指定的,并且在编译之间通常会有所不同。
  3. 任何块静态对象在第一次达到其声明时都会被初始化。由于 C++03 标准不支持线程,这不是线程安全的!
  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具有平凡构造函数的事实,因此TestCase在任何具有非平凡构造的 s 之前初始化。

数据槽() = 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= 的原因)

于 2012-08-23T12:40:04.260 回答
0

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

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

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

于 2012-08-23T12:13:56.747 回答