我可以在 C++ 中的 运行 时间初始化静态常量成员吗?

Can I initialize a static const member at run-time in C++?

是否可以在 运行 期间初始化我的 class 的静态常量成员?这个变量在我的程序中是一个常量,但我想将它作为命令行参数发送。

//A.h
class A {
public: 
    static const int T;
};

//in main method
int main(int argc,char** argv)
{
    //how can I do something like 
    A::T = atoi(argv[1]);
}

如果做不到,我应该使用什么类型的变量?我需要在 运行 时间初始化它并保留常量 属性。

不,你不能那样做。

If this cannot be done what is the type of variable I should use ?

您可以使用非const成员。

class A 
{
   public: 
      static int T;
};

int A::T;

另一种选择是将 T 设为私有成员,将 main 设为好友以便只有它可以修改值,然后通过函数公开该成员。

#include <cstdlib>

class A 
{
   public: 
      static int getT() { return T; }
   private:
      static int T;
      friend int main(int argc, char** argv);
};

int A::T;

int main(int argc, char** argv)
{
   A::T = std::atoi(argv[1]);
   return 0;
}

您不能依赖 main 开始后生成的数据来初始化 static 变量,因为 main 的翻译单元中的静态初始化发生在 main 之前获得控制权,并且其他翻译单元中的静态初始化可能会在 main 翻译单元的静态初始化之前或之后以未指定的顺序发生。

但是,您可以初始化一个隐藏的非常量变量,并提供一个 const 对其的引用,如下所示:

struct A {
public: 
    // Expose T as a const reference to int
    static const int& T;
};

//in main.cpp

// Make a hidden variable for the actual value
static int actualT;
// Initialize A::T to reference the hidden variable
const int& A::T(actualT);

int main(int argc,char** argv) {
    // Set the hidden variable
    actualT = atoi(argv[1]);
    // Now the publicly visible variable A::T has the correct value
    cout << A::T << endl;
}

Demo.

不仅你不能,你也不应该尝试通过弄乱 const_cast 来做到这一点。静态 const 成员很有可能以只读段结束,任何修改它们的尝试都会导致程序崩溃。

不可以,因为您将变量定义为static 和const,所以不能更改它的值。 您必须在定义本身中设置它的值,或者通过在创建 class A.

的对象时调用的构造函数

这里使用单例模式。 在单例 class 中有一个数据成员,您希望在 运行 时间对其进行初始化。一旦创建了单个实例并正确初始化了数据成员,就不会再有覆盖和更改它的风险。

Singleton 将保留数据的奇异性。

希望对您有所帮助。

很抱歉不同意 static const 符号不可能在程序启动时而不是在编译时初始化的评论和回答。

实际上这是可能的,我用过很多次,但我从配置文件中初始化它。类似于:

// GetConfig is a function that fetches values from a configuration file
const int Param1 = GetConfig("Param1");
const int MyClass::Member1 = GetConfig("MyClass.Member1");

如您所见,编译时不一定知道这些静态常量。它们可以从环境中设置,例如配置文件。

另一方面,如果可行的话,从 argv[] 设置它们似乎非常困难,因为当 main() 启动时,静态符号已经初始化。

通常您会有多个配置值。所以把它们放在一个struct里面,正常的全局访问就是const.

const config* Config;
...
main (int argc, char* argv [])
{
Config= new config (argc, argv);
...
}

你可以变得更高级,拥有一个全局 function 到 return 配置,所以普通代码甚至不能改变指针,但更难做到一不小心。

一个头文件公开了 get_config () 供所有人使用,但设置它的方法只有打算这样做的代码知道。

方法#1:初始化一个隐藏的非常量变量,并提供对它的const引用(如dasblinkenlight所示):

class A {
public: 
  static const int &T;
};

static int dummy = 0;
const int &A::T  = dummy;

int main() {
  dummy = 10;
  std::cout << A::T << std::endl;
}

Live Demo

方法 #2:使用非 const 静态成员(如 R Sahu 所示):

class A {
public: 
  static int T;
};

int A::T = 0;

int main() {
  A::T = 10;
}

Live Demo

方法#3:将隐藏的非常量变量声明为class 的私有静态成员,并提供一个静态成员const 引用来连接它。定义一个友元函数作为初始化器:

class A {
  friend void foo(int);
    static int dummy;
public: 
    static const int &T;
};

const int &A::T = A::dummy;
int A::dummy = 0;

void foo(int val) { A::dummy = val; }

int main() {
    foo(10);
    std::cout << A::T << std::endl;
}

Live Demo

方法#4:将隐藏的非常量变量声明为class 的私有静态成员,并提供一个静态成员const 引用来连接它。定义一个静态成员函数作为初始化器:

class A {
    static int dummy;
public: 
    static const int &T;
    static void foo(int val) { A::dummy = val; }
};

const int &A::T = A::dummy;
int A::dummy = 0;

int main() {
    A::foo(10);
    std::cout << A::T << std::endl;
}

Live Demo

奖金:

如果你只想初始化一次你可以将辅助函数更改为:

static void foo(int val) { 
  static bool init = true;
  if(init) A::dummy = val;
  init = false;
}

Live Demo

N-O

所要求的语义都是错误的,你不应该为此使用 static-const

一个static是一个对象或整数类型,具有静态存储持续时间和内部链接。

A const 是一个在应用程序的整个生命周期中都不会更改其值的对象,任何更改它的尝试都会导致 UD 。 (绝大多数此类情况都是定义明确的崩溃

作为这个问题的结果,已经提出了危险的解决方法来模仿隐含的行为。在大多数示例中,一个 static-const-reference 被赋予一个隐藏的 static ,它可以在运行时分配,例如

除了维护此类代码的困难之外,问题仍然是声明的语义实际上并未强制执行。

例如,在整个应用程序运行时保持值 const 可以通过执行 const_cast<int &>(A::T) = 42 来破解,这是完全有效的,完美地定义代码,因为引用的类型不是 const。

What is being sought after here is an class that permits to be initialized only once throughout the application, has internal linkage, and the lifetime of the application.

所以只需制作一个模板 class 即可:

template<typename V> class fixation
{
  bool init = true;
 V val;
  public:

    fixation(V const & v) : init(true), val(v) {}

    fixation & operator=( fixation arg)
    {
      if(init )
      {
        this->val = arg.val;
      }
      this->init = false;
      return *this;
    }


    V get()
    {
      return val;
    }
};

struct A
{
    static fixation<int> T;
};

二次调用如何处理,属于执行决策。在此示例中,该值被完全忽略。其他人可能更喜欢抛出异常、做断言等。

有一个技巧,但您应该避免使用它!这是一个简单的例子来说明原理:

int const& foo(int i) {
    static const int j = (i == 0 ? throw 0 : i); 
    return j; 
}

int main() {
    try { 
        int x = foo(0); // oops, we throw        
    } catch(...) {}

    int x = foo(1); // initialized..
    int y = foo(0); // still works..     
}

小心!

最近我自己也遇到了同样的问题,我发现@A.S.H 的答案是最接近完美的,但是必须这么早初始化变量的事实可能会导致一些问题:

  • 无法使用尚不可用的数据源,例如问题中的 argcargv
  • 某些依赖项可能尚未初始化。例如,许多 GUI 框架不允许在早期创建文本框。这是一个问题,因为如果加载配置文件未能通知用户,我们可能希望显示一个错误文本框。

所以我想到了以下内容:

template <class T>
class StaticConfig
{
public:

    StaticConfig()
    {
        if (!mIsInitialised)
        {
            throw std::runtime_error("Tried to construct uninitialised StaticConfig!");
        }
    }

    const T*
    operator -> () const
    {
        return &mConfig;
    }

private:

    friend class ConfigHandler;

    StaticConfig(const T& config)
    {
        mConfig = config;
        mIsInitialised = true;
    }

    static T mConfig;
    static bool mIsInitialised;
};

template <class T>
T StaticConfig<T>::mConfig;
template <class T>
bool StaticConfig<T>::mIsInitialised = false;

我们将数据设为静态但非常量,这样我们就不必立即对其进行初始化,并且可以在更合适的时间为其分配正确的值。通过 operator -> 的重载提供只读访问权限默认构造函数检查此类型的 StaticConfig 是否已经加载了有效数据,如果没有则抛出。这在实践中不应该发生,而是作为调试辅助工具。私有构造函数允许使用有效数据加载类型。负责加载数据的 ConfigHandler class 成为朋友,因此它可以访问私有构造函数。

当所有依赖项都可用于初始化所有 StaticConfig 类型时,可以在适当的时候短暂地创建一个 ConfigHandler 实例。完成后,可以丢弃 ConfigHandler 实例。在那之后,class 可以简单地包含适当类型的 StaticConfig 作为成员,并以最小的入侵只读访问数据。

Online demonstration.