Pergunta

Como Scott Myers escreveu, você pode tirar vantagem de um relaxamento em sistema tipo 's C ++ para clone declare () para retornar um ponteiro para o tipo real sendo declarado:

class Base
{
    virtual Base* clone() const = 0;
};

class Derived : public Base
{
    virtual Derived* clone() const
};

O compilador detecta que o clone () retorna um ponteiro para o tipo do objecto, e permite Derivado para substituí-lo para devolver um apontador para derivada.

Seria desejável ter clone () retornar um ponteiro inteligente que implica a transferência de semântica de propriedade, como o seguinte:

class Base
{
   virtual std::auto_ptr<Base> clone() const = 0;
};

class Derived : public Base
{
    virtual std::auto_ptr<Derived> clone() const;
};

Infelizmente, o relaxamento das convenções não se aplica aos ponteiros inteligentes templated, eo compilador não permitirá que a substituição.

Assim, parece que eu sou deixado com duas opções:

  1. Tem clone () retornar um ponteiro "burro", e documento que os clientes são responsáveis ??por descartá-lo.
  2. Tem clone () retornar um ponteiro de base inteligente, e tem clientes usar dynamic_cast para salvá-los para um ponteiro Derivado se eles precisarem.

é uma destas abordagens preferido? Ou há uma maneira para eu comer minha transferência de semântica de propriedade e ter o meu tipo forte de segurança também?

Foi útil?

Solução

Depende do seu caso de uso. Se você nunca acha que vai precisar para chamar clone em um objeto derivado cujo tipo de dinâmica que você sabe (lembre-se, todo o ponto de clone é permitir copiar sem saber o tipo dinâmico), então você provavelmente deve retorno um ponteiro mudo e carga que em um ponteiro inteligente no código de chamada. Se não, então você só precisa retornar um smart_ptr e assim você pode se sentir livre para devolvê-lo em todas as substituições.

Outras dicas

Use o público não-virtual / Privado padrão virtual:

class Base {
    public:
    std::auto_ptr<Base> clone () { return doClone(); }
    private:
    virtual Base* doClone() { return new (*this); }
};
class Derived : public Base {
    public:
    std::auto_ptr<Derived> clone () { return doClone(); }
    private:
    virtual Derived* doClone() { return new (*this); }
};

A sintaxe não é tão bom, mas se você adicionar isso ao seu código acima, não é resolver todos os seus problemas?

template <typename T>
std::auto_ptr<T> clone(T const* t)
{
    return t->clone();
}

Eu acho que a semântica função são tão claro neste caso que há pouco espaço para confusão. Então eu acho que você pode usar a versão covariant (aquele retornando um ponteiro muda para o tipo real) com a consciência tranquila, e seu interlocutor vai saber que eles estão recebendo um novo objeto cuja propriedade é transferida para eles.

Tr1::shared_ptr<> pode ser escalado como se fosse um ponteiro não processado.

Eu acho que tem clone () retornar um ponteiro shared_ptr<Base> é uma solução bastante limpo. Você pode converter o ponteiro para shared_ptr<Derived> por meio de tr1::static_pointer_cast<Derived> ou tr1::dynamic_pointer_cast<Derived> caso não é possível determinar o tipo de objeto clonado em tempo de compilação.

Para garantir o tipo de objeto é predictible você pode usar um elenco polimórfica para shared_ptr como esta:

template <typename R, typename T>
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p)
{
    assert( std::tr1::dynamic_pointer_cast<R>(p) );
    return std::tr1::static_pointer_cast<R>(p);
}

A sobrecarga adicionado pelo assert vai ser jogado fora na versão de lançamento.

Essa é uma razão para o uso boost::intrusive_ptr vez de shared_ptr ou auto/unique_ptr. O ponteiro bruto contém a contagem de referência e pode ser usado mais facilmente em situações como esta.

responder para C ++ 14:

#include <memory>

class Base
{
public:
    std::unique_ptr<Base> clone() const
    {
        return do_clone();
    }
private:
    virtual std::unique_ptr<Base> do_clone() const
    {
        return std::make_unique<Base>(*this);
    }
};

class Derived : public Base
{
private:
    virtual std::unique_ptr<Base> do_clone() const override
    {
        return std::make_unique<Derived>(*this);
    }
}

Você poderia ter dois métodos, um clone virtual () que retorna um wrapper ponteiro inteligente em torno do tipo base, e uma clone2 não-virtual () que retorna o tipo correto de ponteiro inteligente.

clone2, obviamente, ser implementado em termos de clone e encapsular o elenco.

Dessa forma, pode obter o ponteiro inteligente mais derivado que você sabe em tempo de compilação. Pode não ser o tipo mais derivado geral, mas utiliza todas as informações disponíveis para o compilador.

Outra opção seria a criação de uma versão do modelo de clone que aceita o tipo que você está esperando, mas que acrescenta mais carga sobre o chamador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top