[英]C++: STL troubles with const class members
這是一個開放式的問題。 有效的C ++。 第3項。 盡可能使用const。 真?
我想做一些在對象生命周期const期間不會改變的東西。 但const帶來了它自己的麻煩。 如果類具有任何const成員,則禁用編譯器生成的賦值運算符。 如果沒有賦值運算符,則類將無法與STL一起使用。 如果要提供自己的賦值運算符,則需要const_cast 。 這意味着更多的喧囂和更多的錯誤空間。 你經常使用const類成員?
編輯:作為一項規則,我努力保持const的正確性,因為我做了很多多線程。 我很少需要為我的類實現復制控制,從不編寫刪除代碼(除非絕對必要)。 我覺得const的當前狀態與我的編碼風格相矛盾。 Const迫使我實現賦值運算符,即使我不需要它。 即使沒有const_cast分配也很麻煩。 您需要確保所有const成員比較相等,然后手動復制所有非const成員。
碼。 希望它能澄清我的意思。 您在下面看到的課程不適用於STL。 您需要為它實現一個賦值,即使您不需要它。
class Multiply {
public:
Multiply(double coef) : coef_(coef) {}
double operator()(double x) const {
return coef_*x;
}
private:
const double coef_;
};
你自己說過你使const“在物體生命期間不會改變的任何東西”。 然而,您抱怨隱式聲明的賦值運算符被禁用。 但隱式聲明的賦值運算符確實改變了有問題的成員的內容! 完全合乎邏輯(根據您自己的邏輯),它正在被禁用。 要么是,要么你不應該聲明成員const。
此外,提供自己的賦值運算符不需要const_cast
。 為什么? 您是否嘗試在賦值運算符中指定您聲明為const的成員? 如果是這樣,為什么你聲明它為const呢?
換句話說,提供您正在遇到的問題的更有意義的描述。 到目前為止你提供的那個以最明顯的方式是自相矛盾的。
我很少使用它們 - 麻煩太大了。 當然,在成員函數,參數或返回類型方面,我總是力求const正確性。
正如AndreyT指出的那樣,在這些情況下,分配(大多數)並沒有多大意義。 問題是vector
(例如)是該規則的一個例外。
從邏輯上講,您將對象復制到vector
,稍后您將獲得原始對象的另一個副本。 從純粹的邏輯觀點來看,不涉及任務。 問題是vector
要求對象無論如何都是可分配的(實際上,所有C ++容器都可以)。 它基本上是一個實現細節(在代碼中的某個地方,它可能分配對象而不是復制它們)的一部分接口。
對此沒有簡單的治療方法。 即使定義自己的賦值運算符並使用const_cast
也無法解決問題。 當你獲得一個const
指針或對一個你知道實際上沒有被定義為const
的對象的引用時,使用const_cast
是完全安全的。 但是,在這種情況下,變量本身被定義為const
- 試圖拋棄const
並賦予它給出未定義的行為。 實際上,它幾乎總是可以工作(只要它不是帶有在編譯時已知的初始化器的static const
),但是不能保證它。
C ++ 11和更新版本為這種情況添加了一些新的曲折。 特別是,不再需要將對象分配以存儲在向量(或其他集合)中。 它們可以移動就足夠了。 這在這種特殊情況下沒有幫助(移動const
對象比分配它更容易)但是在其他一些情況下確實使生活變得更加容易(例如,肯定有可移動但不可分配/可復制的類型)。
在這種情況下,您可以通過添加間接級別來使用移動而不是副本。 如果你創建一個“外部”和一個“內部”對象,內部對象中的const
成員和外部對象只包含指向內部的指針:
struct outer {
struct inner {
const double coeff;
};
inner *i;
};
...然后當我們創建一個outer
實例時,我們定義一個inner
對象來保存const
數據。 當我們需要做一個賦值時,我們做一個典型的移動賦值:將指針從舊對象復制到新對象,並且(可能)將舊對象中的指針設置為nullptr,所以當它被銷毀時,它就贏了試着破壞內部物體。
如果你想要足夠嚴重,你可以在舊版本的C ++中使用(類似)相同的技術。 您仍然使用外部/內部類,但每個賦值將分配一個全新的內部對象,或者您使用類似shared_ptr的東西讓外部實例共享對單個內部對象的訪問權限,並在最后一個外部物體被摧毀
它沒有任何真正的區別,但至少對於管理向量時使用的賦值,在vector
調整自身時,你只有兩個對inner
引用(調整大小是為什么向量需要賦值可以開始)。
編譯時的錯誤很痛苦,但運行時的錯誤是致命的。 使用const的構造可能是代碼的麻煩,但它可能會幫助您在實現它們之前找到錯誤。 我盡可能使用consts。
我盡可能地遵循使用const
的建議,但我同意,當涉及到班級成員時, const
是一個很大的麻煩。
我發現在參數方面我非常小心const
-correctness,但對於類成員卻沒有那么多。 實際上,當我使類成員const
並且它導致錯誤時(由於使用STL容器),我做的第一件事就是刪除const
。
我想知道你的情況......以下所有內容都是假設,因為你沒有提供描述你問題的示例代碼,所以...
我想你有類似的東西:
struct MyValue
{
int i ;
const int k ;
} ;
IIRC,默認賦值運算符將執行逐個成員的賦值,類似於:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
this->k = rhs.k ; // THIS WON'T WORK BECAUSE K IS CONST
return *this ;
} ;
因此,這不會產生。
所以,你的問題是沒有這個賦值運算符,STL容器將不接受你的對象。
就我所見:
operator =
我害怕明白const_cast
是什么意思。
我自己的問題解決方案是編寫以下用戶定義的運算符:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
// DON'T COPY K. K IS CONST, SO IT SHOULD NO BE MODIFIED.
return *this ;
} ;
這樣,如果你有:
MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 2 }
據我所知,它是連貫的。 但我想,閱讀const_cast
解決方案,你想要更像的東西:
MyValue a = { 1, 2 }, b = {10, 20} ;
a = b ; // a is now { 10, 20 } : K WAS COPIED
這意味着operator =
的以下代碼:
MyValue & operator = (const MyValue & rhs)
{
this->i = rhs.i ;
const_cast<int &>(this->k) = rhs.k ;
return *this ;
} ;
但是,那么,你在你的問題中寫道:
我想做一些在對象生命周期const期間不會改變的東西
! 我假設你自己的const_cast
解決方案,k在對象生命周期中發生了變化,這意味着你自相矛盾,因為你需要一個在對象生命周期內不會改變的成員變量, !
接受您的成員變量在其所有者對象的生命周期內將更改的事實,並刪除const。
如果您想保留const
成員,可以將shared_ptr
存儲到STL容器中的const
對象。
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include <vector>
class Fruit : boost::noncopyable
{
public:
Fruit(
const std::string& name
) :
_name( name )
{
}
void eat() const { std::cout << "eating " << _name << std::endl; }
private:
const std::string _name;
};
int
main()
{
typedef boost::shared_ptr<const Fruit> FruitPtr;
typedef std::vector<FruitPtr> FruitVector;
FruitVector fruits;
fruits.push_back( boost::make_shared<Fruit>("apple") );
fruits.push_back( boost::make_shared<Fruit>("banana") );
fruits.push_back( boost::make_shared<Fruit>("orange") );
fruits.push_back( boost::make_shared<Fruit>("pear") );
BOOST_FOREACH( const FruitPtr& fruit, fruits ) {
fruit->eat();
}
return 0;
}
但是,正如其他人已經指出的那樣,如果你想要編譯器生成的復制構造函數,那么在我看來刪除const限定成員往往更容易。
我只在引用或指針類成員上使用const。 我用它來表明不應該改變引用或指針的目標。 你發現,在其他類別的成員上使用它是一個很大的麻煩。
使用const的最佳位置是函數參數,指針和各種引用,常量整數和臨時便利值。
臨時便利變量的一個例子是:
char buf[256];
char * const buf_end = buf + sizeof(buf);
fill_buf(buf, buf_end);
const size_t len = strlen(buf);
那個buf_end
指針永遠不應該指向其他任何地方,所以使它成為const是一個好主意。 與len
相同的想法。 如果buf
的字符串在函數的其余部分中永遠不會改變,那么它的len
也不應該改變。 如果可以,我甚至會在調用fill_buf
之后將buf
更改為const,但C / C ++不會讓你這樣做。
關鍵是海報希望在他的實現中保持const
但仍然希望對象可分配。 該語言不方便地支持這種語義,因為成員的constness位於相同的邏輯級別並且與可賦值性緊密耦合。
然而,具有引用計數實現或智能指針的pImpl
慣用語將完全符合海報的要求,因為可分配性隨后被移出實現並向更高級別對象上升。 實現對象僅被構造/破壞,從而在較低級別永遠不需要賦值。
我想你的說法
如果類具有const任何成員,則禁用編譯器生成的賦值運算符。
可能不對。 我有使用const方法的類
bool is_error(void) const;
....
virtual std::string info(void) const;
....
也用於STL。 那么您的觀察結果可能依賴於編譯器還是僅適用於成員變量?
這不是太難。 制作自己的賦值運算符不會有任何問題。 不需要分配const位(因為它們是const)。
更新
關於const的含義存在一些誤解。 這意味着它永遠不會改變。
如果一個賦值應該改變它,那么它不是const。 如果您只是想阻止其他人更改它,請將其設為私有,並且不提供更新方法。
結束更新
class CTheta
{
public:
CTheta(int nVal)
: m_nVal(nVal), m_pi(3.142)
{
}
double GetPi() const { return m_pi; }
int GetVal() const { return m_nVal; }
CTheta &operator =(const CTheta &x)
{
if (this != &x)
{
m_nVal = x.GetVal();
}
return *this;
}
private:
int m_nVal;
const double m_pi;
};
bool operator < (const CTheta &lhs, const CTheta &rhs)
{
return lhs.GetVal() < rhs.GetVal();
}
int main()
{
std::vector<CTheta> v;
const size_t nMax(12);
for (size_t i=0; i<nMax; i++)
{
v.push_back(CTheta(::rand()));
}
std::sort(v.begin(), v.end());
std::vector<CTheta>::const_iterator itr;
for (itr=v.begin(); itr!=v.end(); ++itr)
{
std::cout << itr->GetVal() << " " << itr->GetPi() << std::endl;
}
return 0;
}
我只會使用const成員iff類本身是不可復制的。 我用boost :: noncopyable聲明了很多類
class Foo : public boost::noncopyable {
const int x;
const int y;
}
但是,如果你想要非常偷偷摸摸並且給自己帶來很多潛在的問題,你可以在沒有任務的情況下實現復制結構,但你必須要小心一點。
#include <new>
#include <iostream>
struct Foo {
Foo(int x):x(x){}
const int x;
friend std::ostream & operator << (std::ostream & os, Foo const & f ){
os << f.x;
return os;
}
};
int main(int, char * a[]){
Foo foo(1);
Foo bar(2);
std::cout << foo << std::endl;
std::cout << bar<< std::endl;
new(&bar)Foo(foo);
std::cout << foo << std::endl;
std::cout << bar << std::endl;
}
輸出
1
2
1
1
已使用placement new運算符將foo復制到bar。
從哲學上講,它看起來像安全性能權衡。 Const用於安全。 據我所知,容器使用賦值來重用內存,即為了性能。 他們可能會使用顯式銷毀和替換新的替代(並且邏輯上它更正確),但是賦值有更高效的機會。 我認為,邏輯冗余要求“可分配”(復制可構造就足夠了),但是stl容器希望更快更簡單。
當然,可以將賦值實現為顯式destroy + placement new以避免const_cast hack
你基本上不想把一個const成員變量放在一個類中。 (同上使用引用作為類的成員。)
Constness真正用於程序的控制流程 - 防止在代碼中錯誤的時間發生變異。 因此,不要在類的定義中聲明const成員變量,而是在聲明類的實例時全部或全部使用它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.