疯狂的谈话(偏执于初始化)

Crazy talk (paranoid about initialization)

本文关键字:初始化 偏执 疯狂      更新时间:2023-10-16

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

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

据我所知,这是确保静态成员初始化为零的唯一方法。然而,它创建了这样的模糊代码:

dataSlot() = 7; // perfectly normal?

另一种方法是将定义放在翻译单元中,并将内容排除在头文件之外。我并不反对这一点,但我不知道标准是什么,在什么时候和什么情况下才是安全的。

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

(注意不要滥用全局变量…)只需在全局范围内声明变量即可。它保证在任何代码运行之前都是零初始化的。

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

返回非常量引用本身是无害的,例如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()?

如果你想让别人阻止你,他们需要更多的信息来提出一种替代你使用可变全局状态的方法!

它被称为Meyers单重态,几乎完全安全。

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

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

不,不是。该标准保证:

  1. 所有具有静态存储(块和文件或类静态作用域)且具有琐碎构造函数的对象都将在任何代码运行之前初始化。程序的任何代码
  2. 在调用main函数之前,所有具有文件/全局/类静态作用域和非平凡构造的对象都将被初始化。可以保证的是,如果对象A和B在同一翻译单元中定义,并且A在B之前定义,则A在B之后初始化。然而,在不同翻译单元中所定义的对象的构造顺序是未指定的,并且在不同的汇编中通常会有所不同
  3. 任何块静态对象在第一次到达它们的声明时都会被初始化。由于C++03标准不支持线程,所以这不是线程安全的
  4. main()函数退出或应用程序使用exit()系统调用终止后,所有具有静态存储的对象(块和文件/global/class-static-scoped)将按照其构造函数完成的相反顺序销毁

这两种方法在任何情况下都不可用且可靠!

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

没有人会阻止你。这是合法且完全合理的做法。但要确保你不会陷入陷阱。

例如,任何合理的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之前初始化

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的方法,编译器就不会查找Type的方法;这就是为什么它需要运算符=)

您可以创建两个函数,dataslot()和set_dataslot(

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

你可能不想在头中内联那么多,但我发现,如果你无论如何都尝试这种方法,一些C++实现会做得很糟糕。