Napisałem następujący kod, który używa unique_ptr<Derived>
tam, gdzie unique_ptr<Base>
jest oczekiwane
class Base {
int i;
public:
Base( int i ) : i(i) {}
int getI() const { return i; }
};
class Derived : public Base {
float f;
public:
Derived( int i, float f ) : Base(i), f(f) {}
float getF() const { return f; }
};
void printBase( unique_ptr<Base> base )
{
cout << "f: " << base->getI() << endl;
}
unique_ptr<Base> makeBase()
{
return make_unique<Derived>( 2, 3.0f );
}
unique_ptr<Derived> makeDerived()
{
return make_unique<Derived>( 2, 3.0f );
}
int main( int argc, char * argv [] )
{
unique_ptr<Base> base1 = makeBase();
unique_ptr<Base> base2 = makeDerived();
printBase( make_unique<Derived>( 2, 3.0f ) );
return 0;
}
i spodziewałem się tego kodu nie skompilować, bo według mojego rozumienia unique_ptr<Base>
i unique_ptr<Derived>
są związane typy i unique_ptr<Derived>
nie jest w rzeczywistości pochodzi od unique_ptr<Base>
tak przyporządkowanie nie powinna działać.
Ale dzięki pewnej magii działa i nie rozumiem dlaczego, a nawet jeśli jest to bezpieczne. Czy ktoś może wyjaśnić, proszę?
c++
templates
inheritance
unique-ptr
Youda008
źródło
źródło
unique_ptr
byłoby raczej bezużyteczne w przypadku dziedziczeniaBase
nie ma wirtualnego destruktora.Odpowiedzi:
Trochę magii, której szukasz, to konstruktor konwertujący # 6 tutaj :
Umożliwia konstruowanie
std::unique_ptr<T>
niejawnie z wygasającego,std::unique_ptr<U>
jeśli (glosowanie przez usuwanie dla jasności):Innymi słowy, naśladuje niejawne konwersje surowego wskaźnika, w tym konwersje pochodne do bazy, i robi to, czego oczekujesz ™ bezpiecznie (pod względem żywotności - nadal musisz upewnić się, że typ podstawowy można usunąć polimorficznie).
źródło
Base
nie wywoła destruktoraDerived
, więc nie jestem pewien, czy to naprawdę bezpieczne. (Trzeba przyznać, że jest nie mniej bezpieczny niż surowy wskaźnik).Ponieważ
std::unique_ptr
ma konstruktor konwertujący jakoi
A
Derived*
może przekształcić sięBase*
domyślnie, w tym przypadku można zastosować konstruktor konwertujący. Wtedy astd::unique_ptr<Base>
może zostać przekonwertowane zstd::unique_ptr<Derived>
niejawnie, tak jak robi to wskaźnik raw. (Uwaga: wartośćstd::unique_ptr<Derived>
musi być wartością konstrukcyjnąstd::unique_ptr<Base>
ze względu na charakterystykęstd::unique_ptr
.)źródło
Można niejawnie skonstruować
std::unique_ptr<T>
wystąpienie z rvalue odstd::unique_ptr<S>
kiedyS
można przekształcićT
. Wynika to z konstruktora # 6 tutaj . W takim przypadku własność zostaje przeniesiona.W twoim przykładzie masz tylko wartości typu
std::uinque_ptr<Derived>
(ponieważ zwracana wartośćstd::make_unique
jest wartością), a gdy używasz tego jako astd::unique_ptr<Base>
, wywoływany jest wspomniany wyżej konstruktor. Przedmiotowestd::unique_ptr<Derived>
obiekty żyją więc tylko przez krótki czas, tzn. Są tworzone, a następnie własność jest przekazywana dostd::unique_ptr<Base>
obiektu, który jest dalej używany.źródło