簡體   English   中英

C ++:復制構造函數:直接使用getter或訪問成員變量?

[英]C++: Copy constructor: Use getters or access member vars directly?

我有一個帶有復制構造函數的簡單容器類。

您是否建議使用getter和setter,或直接訪問成員變量?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • 在示例中,所有代碼都是內聯的,但在我們的實際代碼中沒有內聯代碼。

更新(09年9月29日):

其中一些答案寫得很好但是他們似乎忽略了這個問題的重點:

  • 這是一個簡單的人為例子,討論使用getter / setter和變量

  • 初始化列表或私有驗證器函數實際上不是這個問題的一部分。 我想知道這兩種設計是否會使代碼更容易維護和擴展。

  • 一些ppl在這個例子中專注於字符串,但它只是一個例子,想象它是一個不同的對象。

  • 我不關心表現。 我們不是在PDP-11上編程

編輯:回答編輯的問題:)

這是一個簡單的人為例子, 討論使用getter / setter和 變量

如果您有一個簡單的變量集合,不需要任何類型的驗證,也不需要額外的處理,那么您可以考慮使用POD。 來自Stroustrup的FAQ

精心設計的類為其用戶提供了一個干凈簡單的界面, 隱藏其表示並使用戶不必了解該表示。 如果不應該隱藏表示 - 比如說,因為用戶應該能夠以他們喜歡的方式更改任何數據成員 - 您可以將該類視為“只是一個普通的舊數據結構”

簡而言之,這不是JAVA。 你不應該寫普通的getter / setter,因為它們和它們自己暴露變量一樣糟糕。

初始化列表或私有驗證器函數實際上不是這個問題的一部分。 我想知道這兩種設計是否會使代碼更容易維護和擴展。

如果要復制另一個對象的變量,則源對象應處於有效狀態。 這個生病的源對象是如何在第一時間構建的?! 構造函數不應該做驗證工作嗎? 是不是修改成員函數負責通過驗證輸入來維護類不變量? 為什么要在復制構造函數中驗證“有效”對象?

我不關心表現。 我們不是在PDP-11上編程

這是最優雅的風格,但在C ++中,最優雅的代碼通常具有最佳的性能特征。


您應該使用initializer list 在您的代碼中, m_str1是默認構造的, 然后分配一個新值。 你的代碼可能是這樣的:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak您不應該IMO驗證copy constructor cont.m_str1 我所做的是驗證constructors東西。 copy constructor驗證意味着您首先復制一個格式錯誤的對象,例如:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

您應該使用初始化列表,然后問題變得毫無意義,如:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Matthew Wilson的Imperfect C ++中有一個很棒的部分解釋了所有關於成員初始化列表的內容,以及如何將它們與const和/或引用結合使用以使代碼更安全。

編輯 :顯示驗證和const的示例:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

正如它現在所寫的那樣(沒有輸入或輸出的限定條件)你的getter和setter(如果你願意的話,訪問器和mutator)完全沒有任何成就,所以你也可以將字符串公之於眾,並完成它。

如果真正的代碼確實對字符串進行了限定,那么很有可能你所處理的內容根本就不是一個字符串 - 相反,它只是看起來很像字符串的東西。 你在這種情況下真正做的是濫用類型系統,有點暴露字符串,當真正的類型只是有點像字符串。 然后,您將提供setter以嘗試強制實際類型與真實字符串相比的任何限制。

當你從那個方向看它時,答案變得相當明顯:不是一個字符串,用一個setter來使字符串像其他(更受限制的)類型一樣,你應該做的是為它定義一個實際的類。打字你真的想要。 正確定義該類后,您將其實例公開。 如果(在這里似乎是這種情況),為它分配一個以字符串開頭的值是合理的,那么該類應該包含一個以字符串作為參數的賦值運算符。 如果(在這里似乎也是如此)在某些情況下將該類型轉換為字符串是合理的,它還可以包括生成字符串作為結果的強制轉換運算符。

與在周圍的類中使用setter和getter相比,這提供了真正的改進。 首先,當你將它們放在一個周圍的類中時,該類中的代碼很容易繞過getter / setter,從而失去了setter應該執行的任何強制執行。 其次,它保持正常的符號。 使用getter和setter會強制您編寫簡單難看且難以閱讀的代碼。

C ++中字符串類的主要優點之一是使用運算符重載,因此您可以替換以下內容:

strcpy(strcat(filename, ".ext"));

有:

filename += ".ext";

提高可讀性。 但是看看如果該字符串是強制我們通過getter和setter的類的一部分會發生什么:

some_object.setfilename(some_object.getfilename()+".ext");

如果有的話,C代碼實際上比這個混亂更具可讀性。 另一方面,考慮如果我們正確地完成工作會發生什么,使用定義運算符字符串和operator =的類的公共對象:

some_object.filename += ".ext";

很好,簡單,可讀,就像應該的那樣。 更好的是,如果我們需要對字符串執行某些操作,我們只能檢查那個小類,我們實際上只需要查看一個或兩個特定的,眾所周知的地方(operator =,可能是該類的ctor或兩個)知道它總是被強制執行 - 這是一個完全不同的故事,當時我們正在使用二傳手嘗試做這項工作。

問問自己成本和收益是什么。

成本:更高的運行時開銷。 在ctors中調用虛函數是一個壞主意,但是setter和getter不太可能是虛擬的。

好處:如果setter / getter做了一些復雜的事情,你就不會重復代碼; 如果它做的事情不直觀,你就不會忘記這樣做。

不同類別的成本/效益比會有所不同。 一旦你確定了這個比例,就用你的判斷。 對於不可變類,當然,您沒有setter,並且您不需要getter(因為const成員和引用可以是公共的,因為沒有人可以更改/重置它們)。

你是否預料到字符串是如何返回的,例如。 白色空間修剪,空檢查等? 與SetMyString()相同,如果答案是肯定的,那么您最好使用訪問方法,因為您不必更改您的代碼,只需修改那些getter和setter方法。

如何編寫復制構造函數沒有靈丹妙葯。 如果您的類只有成員提供了一個復制構造函數,該構造函數使用初始化列表創建不共享狀態(或者至少看起來不這樣做)的實例,這是一種好方法。

否則你將不得不考慮。

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

請注意,您可以使用smart_ptr繞過潛在的段smart_ptr - 但是您可以通過調試生成的錯誤來獲得很多樂趣。

當然它可以變得更有趣。

  • 按需創建的成員。
  • 如果你以某種方式介紹多態性, new beta(a.beta)錯誤的

......一個螺絲,否則 -請寫一個拷貝構造函數時,總會想起。

為什么你需要吸氣劑和二傳手?

簡單:) - 它們保留不變量 - 即保證你的類產生,例如“MyString總是有偶數個字符”。

如果按預期實現,則您的對象始終處於有效狀態 - 因此成員副本可以很好地直接復制成員,而不必擔心違反任何保證。 通過另一輪狀態驗證傳遞已經驗證的狀態沒有任何優勢。

正如AraK所說,最好的是使用初始化列表。


不那么簡單(1):
使用getter / setter的另一個原因不是依賴於實現細節。 這對於復制CTor來說是一個奇怪的想法,當改變這樣的實現細節時,你幾乎總是需要調整CDA。


不那么簡單(2):
為了證明我的錯誤,您可以構造依賴於實例本身或其他外部因素的不變量。 一個(非常有條理的)例子:“如果實例的數量是偶數,則字符串長度是偶數,否則它是奇數。” 在這種情況下,復制CTor必須拋出或調整字符串。 在這種情況下,它可能有助於使用setter / getters - 但這不是一般的cas。 你不應該從奇怪的事物中得出一般規則。

我更喜歡使用外部類的接口來訪問數據,以防您想要更改它的檢索方式。 但是,當您處於類的范圍內並希望復制復制值的內部狀態時,我將直接使用數據成員。

更不用說如果沒有內聯getter,你可能會保存一些函數調用。

如果你的獲取者(內聯和)不是virtual ,那么使用它們就沒有優勢也沒有直接成員訪問權限 - 它在風格方面對我來說只是看起來很傻,但是,無論如何都沒有什么大不了的。

如果你的干將都是虛擬的,再有就是開銷......但無論如何這也正是當你想打電話給他們,萬一他們覆蓋在子類- !)

有一個簡單的測試適用於許多設計問題,其中包括:添加副作用,看看有什么中斷。

假設setter不僅分配值,還會寫入審計記錄,記錄消息或引發事件。 你想在復制對象時為每個屬性發生這種情況嗎? 可能不是 - 所以在構造函數中調用setter在邏輯上是錯誤的(即使setter實際上只是賦值)。

雖然我同意其他海報,你的樣本中有許多入門級C ++“禁忌”,但是把它放在一邊並直接回答你的問題:

在實踐中,我傾向於將許多但不是所有的成員字段*公共開始,然后在需要時將它們移動到get / set。

現在,我將第一個說這不一定是推薦的做法,許多從業者會厭惡這一點,並說每個領域都應該有定位器/吸氣劑。

也許。 但我發現在實踐中並不總是必要的。 當然,當我將一個字段從公共字段更改為一個getter時,它會導致疼痛,有時當我知道一個類將具有什么用法時,我會給它set / get並使字段從一開始就受到保護或私有。

因人而異

RF

  • 你調用字段“變量” - 我鼓勵你只將這個術語用於函數/方法中的局部變量

暫無
暫無

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

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