[英]Proxy object/reference getters vs setters?
當我設計一個通用類時,我經常處於以下設計選擇之間的兩難境地:
template<class T>
class ClassWithSetter {
public:
T x() const; // getter/accessor for x
void set_x(const T& x);
...
};
// vs
template<class T>
class ClassWithProxy {
struct Proxy {
Proxy(ClassWithProxy& c /*, (more args) */);
Proxy& operator=(const T& x); // allow conversion from T
operator T() const; // allow conversion to T
// we disallow taking the address of the reference/proxy (see reasons below)
T* operator&() = delete;
T* operator&() const = delete;
// more operators to delegate to T?
private:
ClassWithProxy& c_;
};
public:
T x() const; // getter
Proxy x(); // this is a generalization of: T& x();
// no setter, since x() returns a reference through which x can be changed
...
};
筆記:
x()
和operator T()
返回T
而不是const T&
,是因為如果x
只是隱式存儲,則可能無法從類中獲得對x
的引用(例如假設T = std::set<int>
但是類型T
x_
存儲為std::vector<int>
) x
是不允許 我想知道什么是一些情況,其中一個人更喜歡一種方法而不是另一種方式,尤其是。 就......而言:
?
您可以假設編譯器足夠智能以應用NRVO並完全內聯所有方法。
目前的個人觀察:
(這部分與回答問題無關;它只是作為一種動機,並說明有時一種方法比另一種更好。)
設定方法存在問題的一個特定情況如下。 假設您正在實現具有以下語義的容器類:
MyContainer<T>&
(可變,讀寫) - 允許修改容器及其數據實現 MyContainer<const T>&
(可變,只讀) - 允許修改容器但不修改其數據 const MyContainer<T>
(不可變,讀寫) - 允許修改數據而不是容器 const MyContainer<const T>
(不可變,只讀) - 不修改容器/數據 其中“容器修改”是指添加/刪除元素等操作。 如果我用setter方法實現這個天真:
template<class T>
class MyContainer {
public:
void set(const T& value, size_t index) const { // allow on const MyContainer&
v_[index] = value; // ooops,
// what if the container is read-only (i.e., MyContainer<const T>)?
}
void add(const T& value); // disallow on const MyContainer&
...
private:
mutable std::vector<T> v_;
};
通過引入大量依賴於SFINAE的樣板代碼可以緩解這個問題(例如,通過從實現兩個版本的set()
的專用模板助手派生)。 然而,更大的問題是,這會制動通用接口 ,因為我們需要:
另一方面,雖然基於代理的方法工作得很好:
template<class T>
class MyContainer {
typedef T& Proxy;
public:
Proxy get(const T& value, size_t index) const { // allow on const MyContainer&
return v_[index]; // here we don't even need a const_cast, thanks to overloading
}
...
};
並且通用接口和語義不會被破壞。
我在代理方法中遇到的一個困難是支持Proxy::operator&()
因為可能沒有存儲類型T
對象/可用引用(參見上面的注釋)。 例如,考慮:
T* ptr = &x();
除非x_
實際存儲在某個地方(在類本身中或通過在成員變量上調用的(鏈)方法可訪問),否則無法支持,例如:
template<class T>
T& ClassWithProxy::Proxy::operator&() {
return &c_.get_ref_to_x();
}
這是否意味着當T&
可用時(即顯式存儲x_
),代理對象引用實際上是優越的,因為它允許:
(在這種情況下,兩難就是在void set_x(const T& value)
和T& x()
。)
編輯:我更改了setter / accessors的constpos中的拼寫錯誤
像大多數設計困境一樣,我認為這取決於具體情況。 總的來說,我更喜歡getter和setter模式,因為它更簡單的代碼(不需要每個字段的代理類),更容易被另一個人理解(查看你的代碼),在某些情況下更明確。 但是,在某些情況下,代理類可以簡化用戶體驗並隱藏實現細節。 幾個例子:
如果您的容器是某種關聯數組,則可能會重載operator []以獲取和設置特定鍵的值。 但是,如果尚未定義某個鍵,則可能需要執行特殊操作才能添加該鍵。 這里的代理類可能是最方便的解決方案,因為它可以根據需要以不同的方式處理=賦值。 但是,這可能誤導用戶:如果此特定數據結構具有不同的添加時間與設置,則使用代理會使這很難看到,而使用set和put方法設置可以清楚地表明每個操作使用的單獨時間。
如果容器在T上進行某種壓縮並存儲壓縮形式怎么辦? 雖然您可以使用在必要時執行壓縮/解壓縮的代理,但它會隱藏與用戶進行壓縮/解壓縮相關的成本,並且他們可能會使用它,就像它是一個簡單的分配而沒有繁重的計算。 通過使用適當的名稱創建getter / setter方法,可以更明顯地表明它們需要大量的計算工作。
吸氣劑和制定者似乎也更具可擴展性。 為新字段制作getter和setter很容易,而制作代理可以轉發每個屬性的操作將是一個容易出錯的煩惱。 如果您以后需要擴展容器類怎么辦? 使用getter和setter,只需將它們設置為虛擬並在子類中覆蓋它們。 對於代理,您可能必須在每個子類中創建一個新的代理結構。 為了避免破壞封裝,你可能應該讓你的代理結構使用超類的代理結構來完成一些工作,這可能會讓人很困惑。 使用getter / setter,只需調用super getter / setter即可。
總的來說,getter和setter更容易編程,理解和更改,並且可以顯示與操作相關的成本。 所以,在大多數情況下,我更喜歡它們。
我認為你的ClassWithProxy接口是混合包裝器/代理和容器。 對於容器,通常使用類似的訪問器
T& x();
const T& x() const;
就像標准容器一樣,例如std::vector::at()
。 但通常通過引用訪問成員會破壞封裝。 對於容器來說,它是一種便利,也是設計的一部分。
但是你注意到對T
的引用並不總是可用,所以這將減少ClassWithSetter接口的選項,這應該是T
處理存儲類型的方式的包裝 (當容器處理存儲對象的方式時) )。 我會改變命名,說清楚,它可能不如普通的get / set有效。
T load() const;
void save(const T&);
或者更多的上下文。 現在應該很明顯,通過使用代理修改T
,再次打破封裝。
順便說一句,沒有理由不在容器內部使用包裝器。
我認為你的set
實現可能的一部分問題是你對const MyContainer<T>&
表現如何的想法與標准容器的行為方式不一致,因此可能會混淆未來的代碼維護者。 “常量容器,可變元素”的常規容器類型是const MyContainer<T*>&
,您可以在其中添加間接級別以清楚地向用戶表明您的意圖。
這就是標准容器的工作方式,如果使用該機制,則不需要底層容器是可變的,也不需要將set
函數設置為const
。
所有這些說我稍微喜歡set
/ get
方法,因為如果一個特定的屬性只需要一個get
你根本不需要寫一個set
。
但是我不想寫任何直接訪問成員(如get / set或proxy),而是提供一個有意義的命名接口,客戶端可以通過該接口訪問類功能。 在一個簡單的例子中顯示我的意思,而不是set_foo(1); set_bar(2); generate_report();
set_foo(1); set_bar(2); generate_report();
喜歡generate_report(1, 2);
這樣的直接接口generate_report(1, 2);
並避免直接操縱類屬性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.