在C ++中使全局“常量”的正确方法

时间:2010-07-06 19:30:07

标签: c++ variables

通常,我定义一个真正的全局常量(比方说,pi)的方法是在头文件中放置一个extern const,并在.cpp文件中定义常量:

constants.h:

extern const pi;

constants.cpp:

#include "constants.h"
#include <cmath>
const pi=std::acos(-1.0);

这适用于真正的常量,如pi。但是,我在寻找一个“常量”的最佳实践,因为它将从程序运行到程序运行保持不变,但可能会根据输入文件而改变。这方面的一个例子是引力常数,它取决于所用的单位。 g在输入文件中定义,我希望它是任何对象都可以使用的全局值。我总是听说使用非常量全局变量是不好的做法,因此目前我已将其存储在系统对象中,然后将其传递给它生成的所有对象。然而,随着物体数量的增加,这似乎有点笨拙且难以维持。

思想?

10 个答案:

答案 0 :(得分:3)

这完全取决于您的应用程序大小。如果你真的完全确定一个特定的常量将为你的代码中的所有线程和分支共享单个值,并且将来不太可能改变,那么全局变量最接近地匹配预期的语义,所以最好只使用它。如果需要的话,稍后重构也是微不足道的,特别是如果你对全局变量使用独特的前缀(例如g_)以便它们永远不会与本地人发生冲突 - 这通常是一个好主意。

一般来说,我更喜欢坚持使用YAGNI,并且不要试图盲目地安抚各种编码风格指南。相反,我首先看看他们的理由是否适用于特定情况(如果编码风格指南没有理由,那么它是一个不好的理由),如果它显然没有,那么就没有理由应用该指南对那种情况。

答案 1 :(得分:1)

合法使用单身人士!

单例类常量()和一个设置单位的方法?

答案 2 :(得分:1)

您可以使用后一种方法的变体,创建一个包含所有这些变量的“GlobalState”类,并将其传递给所有对象:

struct GlobalState {
  float get_x() const;

  float get_y() const;

  ...
};

struct MyClass {
  MyClass(GlobalState &s)
  {
    // get data from s here
    ... = s.get_x();
  }
};

它避免了全局变量,如果你不喜欢它们,它会随着需要更多的变量而优雅地增长。

答案 3 :(得分:1)

在运行的生命周期中使用全局变量值是很糟糕的。

在启动时设置一次的值(此后保持“常量”)对于全局来说是完全可以接受的用途。

答案 4 :(得分:1)

我可以理解你所处的困境,但我很遗憾你不能这样做。

单位不应该影响程序,如果你试图在你的程序的核心处理多个不同的单位,你将受到严重的伤害。

从概念上讲,你应该这样做:

       Parse Input
            |
 Convert into SI metric
            |
       Run Program
            |
Convert into original metric
            |
      Produce Output

这可确保您的程序与现有的各种指标完全隔离。因此,如果有一天你以某种方式添加对16世纪法国公制系统的支持,你只需添加正确配置Convert步骤(适配器),也许还可以输入/输出一些(以识别它们)并正确打印它们,但程序的核心,即计算单元,将不受新功能的影响。

现在,如果你要使用一个不那么恒定的常数(例如地球上的重力加速度,这取决于纬度,经度和海拔高度),那么你可以简单地将它作为参数传递,与另一个一起分组常数。

class Constants
{
public:
  Constants(double g, ....);

  double g() const;

  /// ...
private:
  double mG;

  /// ...
};

这可以成为Singleton,但这违背了(有争议的)依赖注入习语。就个人而言,我尽可能多地偏离Singleton,我通常使用我在每种方法中传递的Context类,使得彼此独立地测试方法变得更加容易。

答案 5 :(得分:0)

为什么您当前的解决方案难以维护?您可以随着对象的增长将对象拆分为多个类(模拟参数的一个对象,例如引力常数,一般对象的一个​​对象,等等)

答案 6 :(得分:0)

我对具有可配置项目的程序的典型习惯是创建一个名为“configuration”的单例类。在configuration内部,可以从解析的配置文件,注册表,环境变量等中读取内容。

一般来说,我反对制作get()方法,但这是我的主要例外。如果必须在启动时从某处读取配置项const,则通常不能创建它们,但是可以将它们设置为私有,并使用const get()方法使客户机查看它们为const。

答案 7 :(得分:0)

这实际上让人想起Abrahams&amp; Sons出版的C ++ Template Metaprogramming一书。 Gurtovoy - 有没有更好的方法来管理你的数据,这样你就不会从码到米或者从一个卷到另一个卷得到很差的转换,也许那个班级知道重力是一种形式加速。

此外你已经有了一个很好的例子,pi =一些函数的结果......

const pi=std::acos(-1.0);

那么为什么不将重力作为某些函数的结果,恰好从文件中读取它?

const gravity=configGravity();

configGravity() {
 // open some file
 // read the data
 // return result
}

问题在于,因为在调用main之前管理全局,所以无法为函数提供输入 - 什么配置文件,如果文件丢失或者没有g,该怎么办。

因此,如果您需要进行错误处理,则需要进行稍后的初始化,单例更适合。

答案 8 :(得分:0)

让我们拼出一些规格。所以你要: (1)保存全局信息(重力等)的文件使用它们比可执行文件的运行寿命更长; (2)在所有单位中可见的全局信息(源文件); (3)一旦从文件中读取,您的程序就不允许更改全局信息;

好吧,

(1)建议围绕全局信息的包装器,其构造函数采用ifstream或文件名字符串 reference (因此,在调用构造函数之前,文件必须存在并且在调用析构函数后它仍然存在);

(2)建议包装器的全局变量。此外,您可以确保这是此包装器的 only 实例,在这种情况下,您需要按照建议将其设置为单例。再说一次,你可能不需要这个(你可能没有相同信息的多个副本,只要它是只读信息!)。

(3)从包装器建议一个const getter。因此,样本可能如下所示:

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>//for EXIT_FAILURE

using namespace std;

class GlobalsFromFiles
{
public:
    GlobalsFromFiles(const string& file_name)
    {
        //...process file:
        std::ifstream ginfo_file(file_name.c_str());
        if( !ginfo_file )
        {
            //throw SomeException(some_message);//not recommended to throw from constructors 
            //(definitely *NOT* from destructors)
            //but you can... the problem would be: where do you place the catcher? 
            //so better just display an error message and exit
            cerr<<"Uh-oh...file "<<file_name<<" not found"<<endl;
            exit(EXIT_FAILURE);
        }

        //...read data...
        ginfo_file>>gravity_;
        //...
    }

    double g_(void) const
    {
        return gravity_;
    }
private:
    double gravity_;
};

GlobalsFromFiles Gs("globals.dat");

int main(void)
{
    cout<<Gs.g_()<<endl;
    return 0;
}

答案 9 :(得分:0)

全球不是邪恶的

首先要把它从胸前拿下来:))

我将常量粘贴到结构中,然后创建一个全局实例:

struct Constants
{
   double g;
   // ...
};

extern Constants C = { ...  };

double Grav(double m1, double m2, double r) { return C.g * m1 * m2 / (r*r); }

(短名称也可以,所有科学家和工程师都这样做......)

我已经使用了这样一个事实:局部变量(即成员,参数,函数 - 本地,......)在少数情况下优先于全局变量作为“穷人的方面”:

您可以轻松地将方法更改为

double Grav(double m1, double m2, double r, Constants const & C = ::C) 
{ return C.g * m1 * m2 / (r*r); }  // same code! 

你可以创建一个

struct AlternateUniverse
{
    Constants C; 

    AlternateUniverse()
    {
       PostulateWildly(C);   // initialize C to better values
       double Grav(double m1, double m2, double r) { /* same code! */  }
    }
}

这个想法是在默认情况下编写具有最小开销的代码,并且即使通用常量应该改变也保留实现。


调用范围与来源范围

或者,如果您/您的开发人员更多地采用过程式而非OO样式,则可以使用调用范围而不是源范围,使用全局堆栈值,大致是:

std::deque<Constants> g_constants;

void InAnAlternateUniverse()
{
   PostulateWildly(C);    // 
   g_constants.push_front(C);
   CalculateCoreTemp();
   g_constants.pop_front();
} 


void CalculateCoreTemp()
{
  Constants const & C= g_constants.front();
  // ...
}

调用树中的所有内容都使用“最新”常量。 OYu可以调用相同的的例程 - 无论嵌套多深 - 使用一组备用常量。当然它应该更好地封装,使异常安全,并且对于多线程,你需要线程本地存储(所以每个线程获得它自己的“堆栈”)


计算与用户界面

我们以不同的方式处理您的原始问题:所有内部表示,所有持久数据都使用SI基本单位。转换发生在输入和输出处(例如,即使典型的大小是毫米,它也总是存储为米)。

我无法真正比​​较,但对我们来说非常好。


尺寸分析

其他回复至少暗示了维度分析,例如相应的Boost Library。它可以强制维度的正确性,并可以自动化输入/输出转换。