2012-10-31 17 views
7

Ostatnio napotkałem problem podczas próby zaimplementowania hierarchii klas za pomocą doskonałych konstruktorów przekazywania. Rozważmy następujący przykład:Konflikt między idealnym konstruktorem przekazywania i konstruktorem kopiowania w hierarchii klas

struct TestBase { 
    template<typename T> 
    explicit TestBase(T&& t) : s(std::forward<T>(t)) {} // Compiler refers to this line in the error message 

    TestBase(const TestBase& other) : s(other.s) {} 

    std::string s; 
}; 

struct Test : public TestBase { 
    template<typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(other) {} 
}; 

Kiedy próbuję skompilować kod pojawia się następujący błąd:

Error 3 error C2664: 'std::basic_string<_Elem,_Traits,_Alloc>::basic_string(const std::basic_string<_Elem,_Traits,_Alloc> &)' : cannot convert parameter 1 from 'const Test' to 'const std::basic_string<_Elem,_Traits,_Alloc> &'

Moje zrozumienie jest, że kompilator traktuje doskonały konstruktor przekierowania jako lepszą matematyki niż z skopiować konstruktora. Zobacz na przykład Scott Meyers: Copying Constructors in C++11. W innych implementacjach bez hierarchii klas mogłem wyłączyć idealny konstruktor przekazujący z konstruktora kopiowania przez SFINAE. Zobacz na przykład Martinho Fernandes: Some pitfalls with forwarding constructors. Kiedy próbuję zastosować wymienione rozwiązanie do tego przykładu, nadal nie mogę skompilować się z tym samym komunikatem o błędzie.

Myślę, że jednym z możliwych rozwiązań byłoby uniknięcie perfekcyjnego przekazywania, pobranie parametrów według wartości w konstruktorach i przejście od nich do zmiennych klasy.

Moje pytanie brzmi, czy istnieją inne rozwiązania tego problemu, czy też w takim przypadku nie jest możliwe doskonałe przekazywanie?

Aktualizacja: Okazało się, że moje pytanie łatwo jest źle zrozumieć. Postaram się więc nieco wyjaśnić moje intencje i kontekst.

  • Kod jest kompletny, jak podano w pytaniu. Nie są tworzone żadne inne obiekty ani funkcje. Wystąpił błąd podczas próby kompilacji wysłanego przykładu.
  • Celem konstruowania idealnego konwertera przekazującego jest inicjalizacja członka, a , a nie posiadanie jakiegoś dodatkowego konstruktora kopii. Powodem jest tutaj zapisanie niektórych kopii obiektów podczas inicjowania elementów z obiektami tymczasowymi (zgodnie z propozycją w rozmowach Scotta Meyersa).
  • Niestety, jak się okazało, idealny konstruktor przekazujący może kolidować z innymi przeciążonymi konstruktorami (w tym przykładzie z konstruktorami kopiowania) .
  • Podobnie jak w odpowiedziach i komentarzach na to pytanie zasugerowano: Możliwe rozwiązania polegałyby na wprowadzeniu jawnych rzutów lub posiadaniu oddzielnych niesformowanych konstruktorów (tj. W odniesieniu do przykładu mającego dwa konstruktory o parametrach odpowiednio const string& i string&&).
+0

Możliwe, że napisałeś coś takiego jak 'std :: string (test)' 'zamiast std :: string (test.s) '? Pokaż nam wiersz 130 pliku main.cc – Andrey

+2

Napisałeś 's (std :: forward (t))' zamiast 's (std :: forward (t) .s)'. – avakar

+1

Zwykle mieszanie przeciążeń i szablonów przesyłania dalej jest złym pomysłem. Zrobiłbym pojedynczy konstruktor z 'ciągu' i dodałbym szablon funkcji' make_test' * *. –

Odpowiedz

2

Spróbuj zmienić Test(const Test& other) : TestBase(other) {} do Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

The 2nd Test konstruktor dzwoni TestBase, a istnieją dwie możliwości. Jeden z nich bierze cokolwiek, drugi bierze TestBase. Ale przechodzisz Test na to - "wszystko" pasuje lepiej. Przez jawne rzutowanie na testBase const &, powinniśmy być w stanie uzyskać odpowiedni pasujący do siebie.

Inna możliwość może dotyczyć sposobu konstruowania testu - może to, co przekazałeś w dopasowaniu konstruktora szablonu do testu? Możemy przetestować tę drugą możliwość, usuwając konstruktor szablonu z testu i sprawdzając, czy błąd zniknie.

Jeśli tak, to dlaczego technika nie połączyła się (aby wyłączyć konstruktor szablonu testu, gdy test Wydajności typu wyprowadza test)?

+0

Twoje rozwiązanie kompiluje się bez błędów. Czy podczas stosowania twojego rozwiązania mogą wystąpić jakiekolwiek efekty uboczne? Zwykle lubię być trochę ostrożny przy wyciszaniu błędów/ostrzeżeń przy rzutach. – mkh

+0

Rzut okaże się bezpieczny. Rzucam konstelację Foo & do konstelacji FooBase - coś, co chciałeś robić niejawnie, ale kompilator preferował twój konstruktor szablonu, który akceptuje wszystko. Wyjaśniłem, co chciałeś domyślnie wydarzyć ... – Yakk

+0

Dzięki za wyjaśnienie. Jeszcze jedno pytanie: Jeśli teraz dodaję konstruktor ruchu do Testowania w ten sposób: 'Test (Test i inne): TestBase (std :: move (other)) {}'. Ponownie pojawia się ten sam komunikat o błędzie. Czy masz również rozwiązanie dla tej sytuacji? Zbiorniki dużo! – mkh

1

Przyjrzyjmy się bliżej komunikatowi o błędzie.

std::basic_string<...>::basic_string(const std::basic_string<...> &) :

Oznacza to, że ma ona zastosowanie do konstruktora kopii z std::string

cannot convert parameter 1 from 'const Test' to 'const std::basic_string<..> &

Rzeczywiście, nie ma sposobu, aby przekonwertować z Test do std::string. Jednak Test ma element ciąg, a mianowicie std::string s;.

Wniosek: wygląda na to, że w tym miejscu zapomniałeś dodać .s. Prawdopodobnie jest w s(std::forward<T>(t)).

Innym możliwym powodem jest to, że pierwsze przeciążenie konstruktora zostało wybrane zamiast drugiego dla konstruowania kopii instancji Test.

+0

Nie sądzę, że zapomniałem '.s'. Mój pomysł polegał na tym, że parametr t sam w sobie jest łańcuchem, który chcę przekazać członkowi. Na przykład const char * lub std :: string. – mkh

+0

@mkh: Zatem oznacza to, że T nie jest ciągiem, ale 'Testem'. – Andrey

+0

Niż moje pytanie byłoby, jak mogę zrobić kompilator przestać myśleć, że T może być test. Nie tworzę żadnych obiektów ani nie wywołuję żadnych funkcji. – mkh

1

Następujące powinny pracować i używa żadnych wyraźnych rzutów:

struct Test : public TestBase { 
    private: 
    static TestBase const& toBase(const Test& o) { return o; } 

    public: 
    template <typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(toBase(other)) {} 
}; 
Powiązane problemy