簡體   English   中英

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

[英]Proper way to make a global “constant” in C++

通常,我定義一個真正的全局常量(比方說,pi)的方法是在頭文件中放置一個extern const,並在.cpp文件中定義常量:

constants.h:

extern const pi;

constants.cpp:

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

這適用於像pi這樣的真常量。 但是,我在尋找一個“常量”的最佳實踐,因為它將從程序運行到程序運行保持不變,但可能會根據輸入文件而改變。 這方面的一個例子是引力常數,它取決於所用的單位。 g在輸入文件中定義,我希望它是任何對象都可以使用的全局值。 我總是聽說使用非常量全局變量是不好的做法,因此目前我已將其存儲在系統對象中,然后將其傳遞給它生成的所有對象。 然而,隨着對象數量的增長,這似乎有點笨拙且難以維護。

思考?

這一切都取決於您的應用程序大小。 如果你真的完全確定一個特定的常量將為你的代碼中的所有線程和分支共享單個值,並且將來不太可能改變,那么全局變量最接近地匹配預期的語義,所以最好只使用它。 如果需要的話,稍后重構也是微不足道的,特別是如果你對全局變量使用獨特的前綴(例如g_ )以便它們永遠不會與本地人發生沖突 - 這一般是一個好主意。

一般來說,我更喜歡堅持使用YAGNI,而不是試圖盲目地安撫各種編碼風格指南。 相反,我首先看看他們的理由是否適用於特定情況(如果編碼風格指南沒有理由,那么它是一個不好的理由),如果它顯然沒有,那么就沒有理由應用該指南對那種情況。

合法使用單身人士!

單例類常量()有一個設置單位的方法?

您可以使用后一種方法的變體,創建一個包含所有這些變量的“GlobalState”類,並將其傳遞給所有對象:

struct GlobalState {
  float get_x() const;

  float get_y() const;

  ...
};

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

它避免使用全局變量,如果你不喜歡它們,它會隨着需要更多變量而優雅地增長。

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

在啟動時設置一次的值(此后保持“常量”)對於全局來說是完全可接受的用途。

我能理解你所處的困境,但我很遺憾你不能這樣做。

單位不應該影響程序,如果你試圖在程序的核心處理多個不同的單位,你將受到嚴重傷害。

從概念上講,你應該做這樣的事情:

       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類,使得彼此獨立地測試方法變得更加容易。

為什么您當前的解決方案難以維護? 您可以在對象增長時將對象拆分為多個類(一個對象用於模擬參數,例如引力常量,一個對象用於常規配置,等等)

我對具有可配置項的程序的典型習慣是創建一個名為“configuration”的單例類。 內部configuration可以從解析的配置文件,注冊表,環境變量等中讀取內容。

一般來說,我反對制作get()方法,但這是我的主要例外。 您通常不能使你的配置項const ■如果它們必須從某處在啟動時讀取,但你可以讓他們的私人和使用常量get()方法,以使他們的客戶視圖常量。

這實際上讓人聯想到Abrahams&Gurtovoy的C ++模板元編程書 - 有沒有更好的方法來管理你的數據,這樣你就不會從碼到米或者從一個卷到另一個卷得到很差的轉換,也許那個班級知道重力是一種形式加速。

你也已經有了一個很好的例子,pi =一些函數的結果......

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

那么為什么不將重力作為某種功能的結果,這恰好從文件中讀取?

const gravity=configGravity();

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

問題是因為在調用main之前管理全局,所以你不能在函數中提供輸入 - 什么配置文件,如果文件丟失或者沒有g,該怎么辦。

因此,如果您需要進行錯誤處理,則需要進行稍后的初始化,單例更適合。

我們來說明一些規格。 所以,你想要:(1)保存全局信息(重力等)的文件比使用它們的可執行文件的運行壽命更長; (2)在所有單位中可見的全局信息(源文件); (3)一旦從文件中讀取,您的程序就不允許更改全局信息;

好,

(1)建議圍繞全局信息的包裝器,其構造函數采用ifstream或文件名字符串引用 (因此,文件必須調用構造函數之前存在並且在調用析構函數后它仍然存在);

(2)建議包裝器的全局變量。 此外,您可以確保這是此包裝器的唯一實例,在這種情況下,您需要按照建議將其設置為單例。 然后,你可能不需要這個(你可能沒有相同信息的多個副本,只要它是只讀信息!)。

(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;
}

全球不是邪惡的

不得不先把我的東西放在胸前:)

我將常量粘貼到結構中,並創建一個全局實例:

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可以調用相同的coutines - 無論嵌套多深 - 使用一組備用常量。 當然它應該更好地封裝,使異常安全,並且對於多線程,你需要線程本地存儲(所以每個線程獲得它自己的“堆棧”)


計算與用戶界面

我們以不同方式處理您的原始問題:所有內部表示,所有持久數據都使用SI基本單位。 轉換發生在輸入和輸出(例如,即使典型的大小是毫米,它總是存儲為米)。

我無法真正比​​較,但對我們來說效果很好。


多方面分析

其他回復至少暗示了維度分析,例如相應的Boost Library 它可以強制維度正確性,並可以自動化輸入/輸出轉換。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM