Zincirleme sırasında kopya nasıl kullanılır?


10

Aşağıdaki küçük örnek gibi bir zincirleme türü sınıfı oluşturuyorum. Üye işlevlerini zincirlerken, kopya oluşturucu çalıştırılır. Kopya oluşturucu çağrısından kurtulmanın bir yolu var mı? Aşağıdaki oyuncak örneğimde, sadece geçici işlerle uğraştığım ve bu nedenle "zorunluluk" (belki standartlara göre değil, mantıksal olarak) bir eleme olduğu açıktır. Seçimi kopyalamak için ikinci en iyi seçenek, hareket yapıcısının çağrılmasıdır, ancak durum böyle değildir.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

Düzenleme: Bazı açıklamalar. 1. Bu, optimizasyon seviyesine bağlı değildir. 2. Benim gerçek kod, ints daha karmaşık (örn. Öbek tahsis) nesneleri var


Derleme için hangi optimizasyon seviyesini kullanıyorsunuz?
JVApen

2
auto b = test_class{7};kopya yapıcısını çağırmaz çünkü test_class b{7};derleyiciler bu durumu tanıyacak kadar akıllıdır ve bu nedenle herhangi bir kopyalamayı kolayca kaldırabilirler. Aynı şey yapılamaz b2.
Bazı programcı ahbap

Gösterilen örnekte, taşıma ve kopyalama arasında gerçek veya herhangi bir fark olmayabilir ve herkes bunun farkında değildir. Orada büyük bir vektör gibi bir şey takılırsanız, bu farklı bir sorun olabilir. Normalde hareket sadece türleri kullanarak kaynak için mantıklıdır (çok fazla yığın bellek kullanmak gibi) - durum böyle mi?
Darune

Örnek tartışmalı görünüyor. std::coutKopyalama kasanızda aslında G / Ç ( ) var mı? Bu olmadan kopya optimize edilmelidir.
rustyx

@rustyx, std :: cout'u kaldırın ve kopya yapıcısını açık yapın. Bu, kopya seçiminin std :: cout'a bağımlı olmadığını gösterir.
DDaniel

Yanıtlar:


7
  1. Kısmi yanıt ( b2yerinde oluşturmaz, ancak kopya yapısını bir taşıma yapısına dönüştürür): incrementÜye işlevini ilişkili örneğin değer kategorisine aşırı yükleyebilirsiniz :

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }
    

    Bu neden olur

    auto b2 = test_class{7}.increment();

    taşımak-inşa b2çünkü test_class{7}geçici ve &&aşırı yüklenir test_class::incrementdenir.

  2. Gerçek bir yerinde yapı için (yani bir hareket yapısı bile değil), tüm özel ve özel olmayan üye işlevlerini constexprsürümlere dönüştürebilirsiniz . Sonra yapabilirsin

    constexpr auto b2 = test_class{7}.increment();

    ve ne bir hamle, ne de bir kopya inşaat için ödeme. Açıkçası, bu basit test_class, ancak constexprüye işlevlerine izin vermeyen daha genel bir senaryo için mümkün değildir .


İkinci alternatif de b2değiştirilemez hale getirir .
Bazı programcı dostum

1
@Someprogrammerdude İyi bir nokta. Sanırım constinitoraya gitmek için bir yol olurdu.
lubgr

İşlev adından sonra ve işaretlerinin amacı nedir? Bunu daha önce hiç görmedim.
Philip Nelson

1
@PhilipNelson onlar ref-elemeleri ve üye işlevleri ile thisaynı olanı dikte edebilirconst
kmdreko

1

Temel olarak, bir atamak için bir kurucu, yani bir kopya ya da bir hamle çağrılması gerekir . Bu, işlevin her iki tarafında aynı ayrı nesne olduğu bilinen farklıdır. Ayrıca , işaretçi gibi paylaşılan bir nesneye başvurabilir.


En basit yol muhtemelen kopya oluşturucunun tamamen optimize edilmesini sağlamaktır. Değer ayarı derleyici tarafından zaten optimize edilmiştir, sadece std::coutoptimize edilemeyen değerdir .

test_class(const test_class& t) = default;

(veya hem kopyalama hem de taşıma yapıcısını kaldırın)

canlı örnek


Sorununuz temelde referans olduğundan, bu şekilde kopyalamayı durdurmak istiyorsanız bir çözüm muhtemelen nesneye bir başvuru döndürmüyor.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

Üçüncü bir yöntem, ilk etapta sadece kopya seçimine dayanmaktır - ancak bu, işlemin tek bir işleve yeniden yazılmasını veya kapsüllenmesini gerektirir ve bu nedenle sorunu tamamen önler (bunun istediğiniz şey olmadığının farkında olabilirim, ancak bir diğer kullanıcılara çözüm):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

Dördüncü yöntem bunun yerine bir hamle kullanmaktır.

auto b2 = std::move(test_class{7}.increment());

veya bu cevapta görüldüğü gibi .


@ O'Neil başka bir dava düşünüyordu - ty, düzeltildi
darune
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.