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

时间:2012-08-23 11:46:48

标签: c++

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

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

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

dataSlot() = 7; // perfectly normal?

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

我最不想做的最后一件事是无意中访问未初始化的数据并失去对我的程序的控制。

5 个答案:

答案 0 :(得分:6)

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

对于具有非平凡构造函数的类型,你必须更加狡猾,但是int可以作为全局变量工作。

答案 1 :(得分:4)

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

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

  

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

不,不是。该标准保证:

  1. 在任何代码运行之前,所有具有静态存储的对象(块和文件或类静态范围)与普通构造函数都会被初始化。任何程序代码都可以。
  2. 在调用main函数之前,将初始化具有文件/全局/类静态范围和非平凡构造的所有对象。保证如果对象A和B在同一翻译单元中定义并且A在B之前定义,则A在B之前被初始化。然而,在不同翻译单元中定义的对象的构造顺序是未指定的并且在编译之间通常是不同的。
  3. 首次达到声明时,会初始化任何块静态对象。由于C ++ 03标准对线程没有任何支持,this is NOT thread-safe!
  4. main()函数退出或应用程序退出后,所有具有静态存储的对象(块和文件/全局/类静态作用域)都以与构造函数完成相反的顺序销毁使用exit()系统调用终止。
  5. 在所有情况下,这两种方法都不可用且可靠!

      

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

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

    E.g。任何合理的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的方法;这就是为什么它需要operator =)

答案 4 :(得分:0)

你可以自己制作两个函数,dataslot()和set_dataslot(),它们是实际数据时隙的包装器,有点像这样:

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

你可能不想在标题中插入那么多内容,但我发现如果你尝试那种东西,一些C ++实现会做得相当糟糕。