[英]C++ Encapsulation and references in constructor
當有人傳遞參數時,我無法將封裝的概念(其他人不應直接更改對象數據)和良好的性能建議放在一起。
對於此代碼
// Code only for illustration, I'm not looking for syntax comments (thanks)
class Person{
private:
std::string name;
std::string Id;
public:
Person(std::string _name, std::vector<double> vec)...
我知道最推薦使用的是參數的const
引用(甚至編譯器也警告過)。 但這讓對象依賴於調用它們的代碼嗎?
在上面的代碼中,如果我通過引用傳遞vector
,並且對象需要創建它的副本或修改,不要通過相同的值傳遞?。 當其他人修改引用的向量時,對象會發生什么?
希望這是可讀的
如果你有課
class Person{
std::string name;
public:
Person(std::string _name){
//Body of constructor
}
};
那么在構造函數的主體中有兩個字符串可以訪問:被構造的Person
實例的成員name
,以及_name
的參數_name
。 通常,構造函數會做類似*
Person(std::string _name){
name = _name;
}
上面寫着“將_name
值復制到name
” - 並且具有所需的行為,即name
是一個獨立於其他任何東西的對象。
這樣做的問題是,如果我們在其他地方想要構造Person
的實例並且已經有一個字符串 - 例如我們編寫了代碼
std::string namePassed = "Alice";
Person person(namePassed);
然后編譯器必須生成代碼以按值傳遞namePassed
- 這意味着編譯器會將namePassed
復制到一個新位置,該位置將由構造_name
體內的_name
引用。 這意味着構造過程中發生的整個事件序列如下:
namePassed
被復制到參數_name
。
參數_name
被復制到成員name
。
參數_name
被破壞。
我們真正想要發生的是, namedPassed
會被簡單地復制**到成員name
——我們不想無緣無故地制作臨時副本!
解決這個問題的方法是通過 const 引用傳遞_name
作為
Person(std::string const& _name){
name = _name;
}
因為當我們寫
std::string namePassed = "Alice";
Person person(namePassed);
編譯器說_name
是對namePassed
的引用(本質上是一個指針),然后從該引用復制到name
- 這意味着成員name
仍然以獨立副本結束,但我們避免了額外的復制操作。 如果我們稍后分配給namePassed
,那也沒關系,因為成員name
是一個副本,與namePassed
不共享任何狀態,並且參數_name
將離開作用域***。 const&
限定只適用於臨時參數,不適用於成員。
如果要存儲成員引用,則必須將該成員聲明為常量引用,而不僅僅是參數。 那看起來像這樣:
class Person{
std::string const& name;
public:
Person(std;:string const& _name):name(_name){}
};
在一些特殊情況之外,這是一個壞主意,因為現在只要 Person 的實例存在,對字符串的引用就必須活着,並且對引用字符串的更改將反映在類中 - 但請注意,正在存儲的成員被標記為const&
而不僅僅是參數。
*這里的代碼不是慣用的,因為我想強調復制操作。 更常見的是只寫
Person(std::string const& _name):name(_name){}
它使用初始化列表。 前面的代碼說“默認初始化name
(即初始化一個空字符串),然后為其分配一個值”,而此代碼直接調用帶有給定參數的name
的復制構造函數。
**移動(右值)語義有點復雜,但這有點超出了這個答案的范圍。 值得注意的是,有一個相當常見的構造,其中一個傳遞值然后移動:
Person(std::string _name):name(std::move(_name)){}
這表達了我們可以傳入一個值_name
的想法,該值保證是字符串的獨立副本。 然后,知道它是一個副本,我們可以將其移動到位,這基本上意味着我們竊取了_name
使用的內存並將其交給了name
,這比復制整個字符串便宜得多,而且比編寫單獨的構造函數更不煩人右值和左值。
*** 可能會出現錯誤,但是,如果namePassed
的值在構造函數的執行期間設法更改,但在進行復制之前,則可能會從錯誤的來源復制name
。 這主要是編寫多線程代碼時出現的問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.