5 kuralı - kullanmak veya kullanmamak?


20

3 kuralı ( 5 kural durumları, yeni C ++ standart olarak)

Yıkıcı, kopya oluşturucu veya kopya atama operatörünü kendiniz açıkça bildirmeniz gerekiyorsa, muhtemelen bunların üçünü de açıkça bildirmeniz gerekir.

Ancak, diğer taraftan, Martin'in " Temiz Kodu " tüm boş inşaatçıları ve yıkıcıları kaldırmayı önerir (sayfa 293, G12: Clutter ):

Uygulamasız bir varsayılan kurucu ne işe yarar? Yaptığı tek şey, kodu anlamsız eserler ile karıştırmaktır.

Peki, bu iki karşıt görüş nasıl ele alınır? Boş inşaatçılar / yıkıcılar gerçekten uygulanmalı mıdır?


Sonraki örnek tam olarak ne demek istediğimi gösterir:

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

İle g ++ 4.6.1 kullanarak iyi derler:

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

Yıkıcı struct Aboş ve gerçekten gerekli değil. Öyleyse orada olmalı mı yoksa kaldırılmalı mı?


15
İki alıntı farklı şeyler hakkında konuşur. Yoksa fikrini tamamen özlüyorum.
Benjamin Bannier

1
@honk Ekibimin kodlama standardında, her zaman 4'ü (yapıcı, yıkıcı, kopya kurucuları) beyan etme kuralımız var. Gerçekten bunun mantıklı olup olmadığını merak ediyordum. Boş olsalar bile, gerçekten yıkıcıları ilan etmek zorunda mıyım?
BЈовић

Boş yıkıcılar için şunu düşünün: codesynthesis.com/~boris/blog/2012/04/04/… . Aksi takdirde 3 (5) kuralı benim için mantıklı geliyor, neden 4 kuralı olacağını bilmiyorum
Benjamin Bannier

@honk İnternette bulduğunuz bilgilere dikkat edin. Her şey doğru değil. Örneğin, virtual ~base () = default;
derlemez

@VJovic, Hayır, sanallaştırmanız gerekmedikçe boş bir yıkıcı beyan etmek zorunda değilsiniz. Ve biz konunun üzerindeyken, auto_ptrikisini de kullanmamalısınız .
Dima

Yanıtlar:


44

Başlangıç ​​için kural "muhtemelen" der, bu yüzden her zaman geçerli değildir.

Burada gördüğüm ikinci nokta, eğer üç tanesinden birini bildirmek zorunda kalırsanız, bunun nedeni bellek ayırmak gibi özel bir şey yapmaktır. Bu durumda, diğerleri aynı görevi yürütmek zorunda oldukları için boş olmazlar (dinamik olarak ayrılmış belleğin içeriğini kopya oluşturucuya kopyalamak veya bu belleği boşaltmak gibi).

Sonuç olarak, boş kurucuları veya yıkıcıları ilan etmemelisiniz, ancak birine ihtiyaç duyulursa, diğerlerine de ihtiyaç duyulması muhtemeldir.

Örneğinize gelince: Böyle bir durumda yıkıcıyı dışarıda bırakabilirsiniz. Açıkçası hiçbir şey yapmıyor. Akıllı işaretçilerin kullanımı, 3 kuralının nerede ve neden dayanmadığının mükemmel bir örneğidir.

Aksi halde kaçırmış olabileceğiniz önemli işlevleri uygulamayı unutmuş olmanız durumunda, kodunuzu ikinci kez incelemeniz için bir kılavuzdur.


Akıllı işaretçilerin kullanımı ile yıkıcılar çoğu durumda boştur (kod tabanımdaki yıkıcıların>% 99'u boştur, çünkü hemen hemen her sınıf pimpl deyimini kullanır).
BЈовић

Vay canına, bu çok fazla sivilceye koklamak diyebilirim. Birçok derleyici ile pezevenk optimize etmek daha zor olacaktır (örneğin, satır içi zor).
Benjamin Bannier

@honk Ne demek "pek çok derleyici pezevenk"? :)
BЈовић

@VJovic: üzgünüm, yazım hatası: '
sivilce

4

Burada gerçekten bir çelişki yok. 3 kuralı yıkıcı, kopya oluşturucu ve kopya atama operatörü hakkında konuşur. Bob Amca boş varsayılan kurucular hakkında konuşuyor.

Bir yıkıcıya ihtiyacınız varsa, sınıfınız muhtemelen dinamik olarak ayrılmış belleğe işaretçiler içerir ve muhtemelen bir kopya ctoruna ve operator=()derin bir kopya yapan bir işaretleyiciye sahip olmak istersiniz . Bu, varsayılan bir kurucuya ihtiyacınız olup olmadığına tamamen diktir.

Ayrıca, C ++ 'da boş olsa bile varsayılan bir kurucuya ihtiyaç duyduğunuz durumlar olduğunu unutmayın. Diyelim ki sınıfınızda varsayılan olmayan bir kurucu var. Bu durumda, derleyici sizin için varsayılan bir kurucu oluşturmaz. Bu, bu sınıftaki nesnelerin STL kaplarında saklanamayacağı anlamına gelir, çünkü bu kaplar nesnelerin varsayılan olarak yapılandırılabilir olmasını bekler.

Öte yandan, sınıfınızın nesnelerini STL konteynırlarına koymayı planlamıyorsanız, boş bir varsayılan kurucu kesinlikle işe yaramaz dağınıklıktır.


2

Burada, varsayılan bir kurucu / atama / yıkıcıya eşdeğer (*) değerinizin bir amacı vardır: sorunla ilgili yaşadığınız gerçeği belgelemek ve varsayılan davranışın doğru olduğunu belirlemek. BTW, C ++ 11'de işler =defaultbu amaca hizmet edip edemeyeceğini bilmek için yeterince stabil hale gelmedi .

(Başka bir potansiyel amaç daha vardır: Varsayılan satır içi satır yerine satır dışı bir tanım sağlayın, bunu yapmak için herhangi bir nedeniniz varsa açıkça belgelemek daha iyidir).

(*) Potansiyel çünkü üç kuralın uygulanmadığı gerçek bir hayat vakasını hatırlamıyorum, birinde bir şey yapmak zorunda kalırsam diğerlerinde bir şey yapmak zorunda kaldım.


Bir örnek ekledikten sonra düzenleyin. auto_ptr kullanan örneğiniz ilginç. Akıllı bir işaretçi kullanıyorsunuz, ancak işe uygun bir işaretçi kullanmıyorsunuz. Yaptığınızı yapmaktan ziyade - özellikle durum sık sık meydana gelirse - yazmayı tercih ederim. (Yanılmıyorsam, ne standart ne de destek bir tane sağlar).


Örnek benim açımdan bunu gösteriyor. Yıkıcı gerçekten gerekli değildir, ancak 3 kuralı orada olması gerektiğini söyler.
BЈовић

1

5 kuralı, 3 kuralı, olası nesne kötüye kullanımına karşı bir kautelatif davranış olan kaulatif bir uzantıdır.

Bir yıkıcıya ihtiyacınız varsa, varsayılandan başka bir "kaynak yönetimi" yaptığınız anlamına gelir (sadece değerleri yapılandırın ve yok edin ).

Kopyalama, atama, taşıma ve varsayılan kopyalama değerlerine göre aktarma yaptığından, yalnızca değerleri tutmuyorsanız ne yapacağınızı tanımlamanız gerekir.

Bununla birlikte, taşımayı tanımlarsanız C ++ kopyayı ve kopyayı tanımlarsanız taşımayı siler. Çoğu durumda, bir değeri taklit etmek isteyip istemediğinizi (dolayısıyla mut mut klon kaynağını kopyalayın ve taşımanın bir anlamı yoktur) veya bir kaynak yöneticisini (ve dolayısıyla kopyanın hiçbir anlamı olmadığı kaynağı taşıyın) tanımlamanız gerekir. 3'ü diğerinin kuralı olur 3 )

Hem kopyalama hem de taşıma (5 kuralı) tanımlamanız gereken durumlar oldukça nadirdir: tipik olarak, farklı nesnelere verilirse kopyalanması gereken "büyük bir değere" sahipsiniz, ancak geçici bir nesneden alındığında (kaçınılması) sonra bir klon yok ). STL kapları veya aritmetik kaplar için durum böyledir.

Onlar çünkü onlar destek kopyasına sahip: Olgu matrisler olabilir vardır , değerleri ( a=b; c=b; a*=2; b*=3;birbirini etkilemeyen olmalıdır) ama onlar (aynı zamanda destekleyici hareket ettirerek optimize edilebilir a = 3*b+4*cbir etmiştir +iki geçiciri alır ve geçici oluşturduğu: kaçınarak klonu ve silme olabilir işe yarar)


1

Üç kuralın farklı bir ifadesini tercih ediyorum, ki bu daha makul görünüyor, yani "sınıfınız bir yıkıcıya ihtiyaç duyuyorsa (boş bir sanal yıkıcıdan başka) muhtemelen bir kopya oluşturucu ve atama operatörüne ihtiyaç duyar."

Yıkıcıdan tek yönlü bir ilişki olarak belirtmek birkaç şeyi daha net hale getirir:

  1. Yalnızca optimizasyon olarak varsayılan olmayan bir kopya oluşturucu veya atama operatörü sağladığınız durumlarda geçerli değildir.

  2. Kuralın nedeni, varsayılan kopya oluşturucu veya atama operatörünün manuel kaynak yönetimini bozabilmesidir. Kaynakları manuel olarak yönetiyorsanız, bunları serbest bırakmak için bir yıkıcıya ihtiyacınız olacağını fark etmiş olabilirsiniz.


-3

Tartışmada henüz bahsedilmeyen başka bir nokta daha var: Bir yıkıcı her zaman sanal olmalıdır.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

Yapıcı, tüm türetilmiş sınıflarda da sanal olması için temel sınıfta sanal olarak bildirilmelidir. Yani, temel sınıfınız bir yıkıcıya ihtiyaç duymasa bile, boş bir yıkıcı ilan edip uygularsınız.

(-Wall -Wextra -Weffc ++) üzerindeki tüm uyarıları yaparsanız g ++ sizi bu konuda uyaracaktır. Herhangi bir sınıfta her zaman sanal bir yıkıcı ilan etmenin iyi bir uygulama olduğunu düşünüyorum, çünkü sınıfınızın sonunda temel bir sınıf olup olmayacağını asla bilemezsiniz. Sanal yıkıcıya ihtiyaç duyulmazsa zarar vermez. Öyleyse, hatayı bulmak için zaman kazanırsınız.


1
Ama sanal kurucuyu istemiyorum. Bunu yaparsam, herhangi bir yönteme yapılan her çağrı sanal gönderme kullanır. btw, c ++ 'da "sanal kurucu" diye bir şeyin olmadığını not eder. Ayrıca, örneği çok yüksek uyarı seviyesi olarak derledim.
BЈовић

Gcc'nin uyarısı için kullandığı kural ve genellikle zaten uyguladığım kural IIRC, sınıfta başka sanal yöntemler varsa sanal bir yıkıcı olması gerektiğidir.
Jules
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.