Neden varsayılan taşıma ataması / taşıma yapıcısı yok?


89

Ben basit bir programcıyım. Sınıf üyelerim değişkenleri çoğunlukla POD türleri ve STL kapsayıcılarından oluşur. Bu nedenle, bunlar varsayılan olarak uygulandığı için nadiren atama işleçleri yazmak veya oluşturucuları kopyalamak zorunda kalıyorum.

Buna ek olarak, std::movehareket ettirilemeyen nesnelerde kullanırsam, atama operatörünü kullanır, yani std::movetamamen güvenlidir.

Basit bir programcı olduğum için, yazdığım her sınıfa bir taşıma yapıcısı / atama operatörü eklemeden taşıma yeteneklerinden yararlanmak istiyorum, çünkü derleyici bunları " this->member1_ = std::move(other.member1_);..." olarak uygulayabilir.

Ama öyle değil (en azından Visual 2010'da değil), bunun özel bir nedeni var mı?

Daha önemlisi; Bu aşmanın bir yolu var mı?

Güncelleme: GManNickG'nin cevabına bakarsanız, bunun için harika bir makro sağlar. Ve eğer bilmiyorsanız, hareket-anlambilimini uygularsanız, takas üye işlevini kaldırabilirsiniz.


5
derleyicinin varsayılan bir hareket ctor oluşturmasını sağlayabileceğinizi biliyorsunuz
aaronman

3
std :: move bir hareket yapmaz, basitçe bir l-değerinden bir r-değerine çevirir. Taşıma, hala hareket oluşturucu tarafından gerçekleştirilir.
Owen Delahoy

1
Hakkında MyClass::MyClass(Myclass &&) = default;mı konuşuyorsun
Sandburg

Evet, bugünlerde :)
Viktor Sehr

Yanıtlar:


76

Hareket oluşturucuların ve atama operatörlerinin örtük üretimi tartışmalı olmuştur ve C ++ Standardının son taslaklarında büyük revizyonlar yapılmıştır, bu nedenle şu anda mevcut derleyiciler örtük üretime göre muhtemelen farklı davranacaktır.

Sorunun geçmişi hakkında daha fazla bilgi için 2010 WG21 makaleler listesine bakın ve "mov" araması yapın

Mevcut spesifikasyon (Kasım ayından itibaren N3225) (N3225 12.8 / 8) şunları belirtir:

Bir sınıfın tanımı, bir Xhareket yapıcısını açıkça bildirmezse, yalnızca ve yalnızca aşağıdaki durumlarda biri örtük olarak varsayılan olarak bildirilir.

  • X kullanıcı tanımlı bir kopya oluşturucusuna sahip değil ve

  • X kullanıcının beyan ettiği kopya atama operatörüne sahip değildir

  • X kullanıcı tanımlı bir taşıma atama operatörüne sahip değil,

  • X kullanıcı tanımlı bir yıkıcıya sahip değil ve

  • taşıma yapıcısı örtük olarak silinmiş olarak tanımlanmayacaktır.

12.8 / 22'de, taşıma atama operatörünün örtük olarak varsayılan olarak bildirildiğini belirten benzer bir dil vardır. N3203'te örtük hareket oluşturmanın mevcut belirtimini desteklemek için yapılan değişikliklerin tam listesini bulabilirsiniz : Büyük ölçüde Bjarne Stroustrup'un N3201 adlı makalesi tarafından önerilen çözümlerden birine dayanan örtük hareketler oluşturma koşullarını sıkılaştırma: Devam etmek .


4
Burada örtük (taşıma) yapıcı / atama ilişkilerini açıklayan bazı diyagramlar içeren küçük bir makale yazdım: mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny

Öyleyse, polimorfik temel sınıflarda boş yıkıcıları sadece sanal olarak belirtmek için tanımlamam gerektiğinde, taşıma yapıcısını ve atama operatörünü de açıkça tanımlamam gerekiyor :(.
someguy

@James McNellis: Bu daha önce denediğim bir şeydi, ancak derleyici bundan hoşlanmadı. Bu cevapta hata mesajını gönderecektim, ancak hatayı yeniden oluşturmaya çalıştıktan sonra, bundan bahsettiğini fark ettim cannot be defaulted *in the class body*. Yani, yıkıcıyı dışarıda tanımladım ve işe yaradı :). Yine de biraz garip buluyorum. Bir açıklaması olan var mı? Derleyici gcc 4.6.1
someguy

3
Belki C ++ 11 onaylandığına göre bu yanıta bir güncelleme alabilir miyiz? Davranışların ne kazandığını merak ediyorum.
Joseph Garvin

2
@Guy Avraham: Sanırım söylediğim şey (7 yıl oldu), eğer kullanıcı tarafından beyan edilmiş bir yıkıcıya sahipsem (boş bir sanal bile olsa), hiçbir hareket oluşturucunun dolaylı olarak varsayılan olarak ilan edilmeyeceğidir. Sanırım bu kopya anlambilimiyle sonuçlanır? (Yıllardır C ++ 'ya dokunmadım.) James McNellis daha sonra bunun virtual ~D() = default;işe yaraması ve yine de örtük bir hareket oluşturucusuna izin vermesi gerektiğini söyledi .
someguy

13

Örtük olarak oluşturulan hareket oluşturucular standart için dikkate alınmıştır, ancak tehlikeli olabilir. Dave Abrahams'ın analizine bakın .

Bununla birlikte, sonuçta standart, oldukça önemli bir sınırlama listesi olmasına rağmen, taşıma oluşturucularının ve taşıma atama operatörlerinin örtük üretimini içeriyordu:

Bir X sınıfının tanımı açıkça bir taşıma yapıcısı bildirmiyorsa, yalnızca ve yalnızca
- X'in kullanıcı tarafından bildirilen bir kopya oluşturucusu
yoksa , - X'in kullanıcı tarafından bildirilen bir kopya atama operatörü yoksa ,
- X'in kullanıcı tarafından bildirilen bir taşıma atama işleci yok,
- X'in kullanıcı tarafından bildirilmiş bir yıkıcısı yok ve
- taşıma yapıcısı örtük olarak silinmiş olarak tanımlanmayacak.

Yine de hikayenin tamamı bu değil. Bir ctor bildirilebilir, ancak yine de silinmiş olarak tanımlanabilir:

Örtük olarak bildirilmiş bir kopyala / taşı yapıcısı, sınıfının satır içi genel üyesidir. X sınıfı için varsayılan bir kopyalama / taşıma yapıcısı, X'in aşağıdakilere sahip olması durumunda silinmiş (8.4.3) olarak tanımlanır:

- önemsiz olmayan bir karşılık gelen kurucuya sahip bir değişken üye ve X, birleşim benzeri bir sınıftır,
- aşırı yük çözünürlüğü (13.3) nedeniyle kopyalanamayan / taşınamayan, sınıf tipi M'nin (veya bunun dizisi) statik olmayan bir veri üyesi, M'nin karşılık gelen yapıcısına uygulandığında, bir belirsizlik veya varsayılan kurucudan silinen veya erişilemeyen bir işlevle sonuçlanır -
B'nin karşılık gelen yapıcısına uygulandığı gibi aşırı yük çözünürlüğü (13.3) nedeniyle kopyalanamayan / taşınamayan doğrudan veya sanal bir temel B sınıfı varsayılan kurucudan silinen veya erişilemeyen bir belirsizlik veya işlevle sonuçlanır,
- varsayılan kurucudan silinen veya erişilemez bir yıkıcıya sahip herhangi bir doğrudan veya sanal temel sınıf veya statik olmayan veri üyesi,
- kopya oluşturucu için, rvalue başvuru türünün statik olmayan bir veri üyesi veya
- hareket oluşturucu için, bir hareket yapıcısı olmayan ve önemsiz olmayan bir türe sahip statik olmayan bir veri üyesi veya doğrudan veya sanal temel sınıf için kopyalanabilir.


Mevcut çalışma taslağı, belirli koşullar altında örtük hareket üretimine izin veriyor ve bence karar büyük ölçüde Abrahams'ın endişelerine hitap ediyor.
James McNellis

Tweak 2 ve Tweak 3 arasındaki örnekte hangi hamlenin bozulabileceğini anladığımdan emin değilim. Bunu açıklayabilir misiniz?
Matthieu M.

@Matthieu M .: Tweak 2 ve Tweak 3 bozuldu ve oldukça benzer şekillerde, gerçekten. Tweak 2'de, hareket ctoru tarafından kırılabilen değişmezleri olan özel üyeler var. Tweak 3'te, sınıfın kendisi özel üyelere sahip değildir , ancak özel kalıtımı kullandığından, tabanın genel ve korumalı üyeleri, türevin özel üyeleri olur ve bu da aynı soruna yol açar.
Jerry Coffin

1
Hareket oluşturucunun sınıf değişmezini nasıl kıracağını gerçekten anlamadım Tweak2. Sanırım bunun Numbertaşınacağı ve vectorkopyalanacağı gerçeğiyle bir ilgisi var ... ama emin değilim: / Sorunun artacağını anlıyorum Tweak3.
Matthieu M.

Verdiğiniz bağlantı ölmüş gibi görünüyor?
Wolf

8

(şimdilik aptal bir makro üzerinde çalışıyorum ...)

Evet, o yola ben de gittim. İşte makronuz:

// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(Uzunluk ve belgesel olan gerçek yorumları kaldırdım.)

Sınıfınızdaki tabanları ve / veya üyeleri önişlemci listesi olarak belirtirsiniz, örneğin:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

Ve ortaya bir hareket kurucu ve hareket atama operatörü çıkıyor.

(Bir kenara gelecek olursak, ayrıntıları tek bir makroda nasıl birleştirebileceğimi bilen biri varsa, bu harika olurdu.)


Çok teşekkür ederim, benimki oldukça benzer, ancak üye değişkenlerin sayısını bir argüman olarak iletmek zorunda kaldım (ki bu gerçekten berbat).
Viktor Sehr

1
@Viktor: Sorun değil. Çok geç değilse, diğer cevaplardan birini kabul edildi olarak işaretlemeniz gerektiğini düşünüyorum. Benimki daha çok "bu arada, işte bir yol" gibiydi ve gerçek sorunuzun cevabı değil.
GManNickG

1
Makronuzu doğru okursam, derleyiciniz varsayılan taşıma üyelerini uygular uygulamaz, yukarıdaki örnekleriniz kopyalanamaz hale gelecektir. Örtülü kopya üyelerinin oluşturulması, açıkça bildirilmiş hareket üyeleri mevcut olduğunda engellenir.
Howard Hinnant

@Howard: Sorun değil, o zamana kadar geçici bir çözüm. :)
GManNickG

Gman: Bir takas işlevi varsa, bu makro moveconstructor \ ata ekler:
Viktor Şehr

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.