C ++ 'ı uzun zaman önce öğrendiğimde, C ++' ın noktasının bir kısmının, döngülerin "döngü değişmezlerine" sahip olması gibi, sınıfların da nesnenin ömrü ile ilişkili değişmezleri olduğu gerçeği vurgulandı - doğru olması gereken şeyler çünkü nesne canlı. Yapıcılar tarafından kurulması ve yöntemlerle korunması gereken şeyler. Kapsülleme / erişim kontrolü, değişmezleri zorlamanıza yardımcı olmak için oradadır. RAII bu fikirle yapabileceğiniz bir şeydir.
C ++ 11'den beri artık hareket semantiği var. Hareket etmeyi destekleyen bir sınıf için, bir nesneden hareket etmek resmi olarak ömrünü sona erdirmez - hareketin onu bir "geçerli" durumda bırakması gerekir.
Bir sınıfı tasarlarken, sınıfın değişmezleri sadece taşındığı noktaya kadar korunacak şekilde tasarlarsanız kötü bir uygulama mıdır? Ya da daha hızlı gitmenizi sağlayacaksa , bu tamam mı?
Somut hale getirmek için, benim gibi kopyalanamayan ama taşınabilir bir kaynak türüne sahip olduğunu varsayalım:
class opaque {
opaque(const opaque &) = delete;
public:
opaque(opaque &&);
...
void mysterious();
void mysterious(int);
void mysterious(std::vector<std::string>);
};
Ve her ne sebeple olursa olsun, belki de bazı mevcut dağıtım sistemlerinde kullanılabilmesi için bu nesne için kopyalanabilir bir ambalaj yapmam gerekiyor.
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { o_->mysterious(); }
void operator()(int i) { o_->mysterious(i); }
void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};
Bu copyable_opaque
nesnede, inşaatta kurulan sınıfın değişmezi, o_
varsayılan bir ctor olmadığı ve bir kopya ctor olmayan tek ctor bunları garanti ettiği için üyenin daima geçerli bir nesneyi işaret etmesidir. Tüm operator()
yöntemler bu değişmezin tuttuğunu varsayar ve daha sonra korur.
Ancak, nesne hareket ettirilirse, o o_
zaman hiçbir şeye işaret etmez. Ve bu noktadan sonra, yöntemlerden herhangi birini çağırmak operator()
UB / çökmesine neden olur.
Nesne hiçbir zaman taşınmazsa, değişmez dtor çağrısına kadar korunur.
Diyelim ki bu sınıfı yazdım ve aylar sonra hayali iş arkadaşım UB'yi deneyimledi, çünkü bu nesnelerin çoğunun bir nedenden dolayı karıştırıldığı bazı karmaşık işlevlerde, bu şeylerden birinden taşındı ve daha sonra yöntemleri. Açıkçası gün sonunda onun hatası, ama bu sınıf "kötü tasarlanmış?"
Düşünceler:
Onlara dokunursanız patlayan zombi nesneleri oluşturmak genellikle C ++ 'da kötü bir formdur.
Eğer bir nesne inşa edemezseniz, değişmezleri kuramazsanız, o zaman kraterden bir istisna atarsınız. Değişmezleri bir yöntemle koruyamazsanız, bir şekilde bir hata sinyali verin ve geri alın. Bu, taşınan nesneler için farklı mı olmalı?Sadece "bu nesne taşındıktan sonra, onunla yok etmek dışında herhangi bir şey yapmak yasadışıdır (UB)" başlığını belgelemek yeterli mi?
Her yöntem çağrısında geçerli olduğunu sürekli olarak iddia etmek daha mı iyi?
Şöyle ki:
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { assert(o_); o_->mysterious(); }
void operator()(int i) { assert(o_); o_->mysterious(i); }
void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};
İddialar davranışı önemli ölçüde iyileştirmez ve yavaşlamaya neden olur. Projeniz her zaman iddialarla çalışmak yerine "sürüm oluştur / hata ayıklama derlemesi" şemasını kullanıyorsa, sürüm derlemesindeki çekler için ödeme yapmadığınız için daha cazip olduğunu düşünüyorum. Aslında hata ayıklama yapıları yoksa, bu oldukça çirkin görünüyor.
- Sınıfı kopyalanabilir kılmak daha iyidir, ancak taşınabilir değil mi?
Bu da kötü görünüyor ve bir performans isabetine neden oluyor, ancak "değişmez" sorunu basit bir şekilde çözüyor.
Burada ilgili "en iyi uygulamalar" olarak neyi düşünürdünüz?