Pergunta

Atualmente, estou sofrendo um peido de cérebro. Eu tenho feito isso antes, mas eu não consigo lembrar a sintaxe exata e eu não posso olhar para o código que eu escrevi porque eu estava trabalhando em outra empresa no momento. Eu tenho este arranjo:

class P
{
// stuff
};

class PW : public P
{
// more stuff
};

class PR : public P
{
// more stuff
};

class C
{
public:
    P GetP() const { return p; }    
private:
    P p;
};

// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Agora eu gostaria de fazer P intercambiável com PW e PR (e, portanto, PW e PR podem ser intercambiados). Eu provavelmente poderia fugir com elencos mas essa mudança de código ocorreu algumas vezes só neste módulo. Eu tenho quase certeza que é um operador mas para a vida de mim eu não consigo lembrar o.

Como posso fazer intercambiáveis ??P com PW e PR com uma quantidade mínima de código?

Update: Para dar um pouco mais esclarecimentos. P significa Projecto e o R e W representa leitor e gravador respectivamente. Tudo o leitor é o código para o carregamento - nenhuma variável, e o escritor tem código para simplesmente escrever. Ele precisa ser separado porque as seções de leitura e escrita tem várias classes gerente e diálogos que é nenhum dos Projetos preocupação real que é a manipulação de arquivos do projeto.

Update: Eu também preciso ser capaz de chamar os métodos de P e PW. Então, se P tem um método a () e PW como uma chamada de método b (), então eu poderia:

PW p = c.GetP();
p.a();
p.b();

É basicamente para fazer a conversão transparente.

Foi útil?

Solução

Se você quiser obter essa parte para compilar:



// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Você precisa ser capaz de construir / converter um P em um PW ou PR. Você precisa fazer algo como isto:



class PW : public P
{
    PW(const P &);
// more stuff
};

class PR : public P
{
    PR(const P &);
// more stuff
};

Ou você quis dizer algo mais parecido com:


class P
{
    operator PW() const;
    operator PR() const;
// stuff
};

Outras dicas

Você está tentando coagir variáveis ??reais, ao invés de ponteiros. Para isso seria necessário um elenco. No entanto, se a sua definição de classe ficou assim:

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

Então, se p era um ponteiro para um P, um PW, ou PR, sua função não mudaria, e todas as funções (virtuais), solicitou à P * retornado pela função usaria a implementação de P, PW ou PR, dependendo do que o membro p era ..

Eu acho que a principal coisa a lembrar é a substituição de Liskov . Desde PW e PR são subclasses de P, eles podem ser tratados como se fossem Ps. No entanto, PWS não pode ser tratado como RP, e vice-versa.

No código acima, você tem o oposto do corte problema.

O que você está tentando fazer é atribuir a partir de um P a um PW ou PR que contêm mais informações do que o objeto de origem. Como você faz isso? Say P tem apenas 3 variáveis ??de membro, mas PW tem 12 membros adicionais -? Onde é que os valores para estes vêm quando você escreve PW p = c.GetP()

Se esta atribuição realmente é válida, o que realmente deve indicar algum tipo de estranheza design, então eu iria implementar PW::operator=(const P&) e PR::operator=(const P&), PW::PW(const P&) e PR::PR(const P&). Mas eu não iria dormir muito bem naquela noite.

Este tipo de faz algo sensível, dado que tudo está sendo passado por valor. Não tenho certeza se é o que você estava pensando.

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};

Para fazer PW e PR utilizável através de um P você precisa usar referências (ou ponteiros). Então, você realmente precisa t alterar a interface de C para que ele retorna uma referência.

O principal problema no código antigo era que você estava copiando um P em um PW ou PR. Isso não vai funcionar como o PW e PR, potencialmente, ter mais informações do que um P e de uma perspectiva tipo de um objeto do tipo P não é um PW ou PR. Embora PW e PR são ambos P.

Alterar o código para isso e ele irá compilar: Se você quiser retornar diferentes objetos derivados de uma classe P em tempo de execução, em seguida, a classe C deve ser potencialmente capaz de armazenar todos os tipos diferentes que você espera e ser especializado em tempo de execução. Assim, na classe abaixo eu permitir que você se especializar passando um ponteiro para um objeto que será retornado por referência. Para certificar-se que o objeto é salvo exceção que eu ter embrulhado o ponteiro em um ponteiro inteligente.

class C
{
    public:
        C(std::auto_ptr<P> x):
            p(x)
        {
            if (p.get() == NULL) {throw BadInit;}
        }
        // Return a reference.
        P& GetP() const { return *p; }        
    private:
        // I use auto_ptr just as an example
        // there are many different valid ways to do this.
        // Once the object is correctly initialized p is always valid.
        std::auto_ptr<P> p;
};

// ...
P&  p = c.GetP( );                   // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid  Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid  Thorws exception if not PR
// ...

Talvez você quer dizer o operador dynamic_cast ?

Eles não são totalmente intercambiáveis. PW é um P. PR é um P. Mas P não é necessariamente um PW, e isso não é necessariamente um PR. Você pode usar static_cast aos ponteiros do elenco de PW * a P *, ou a partir PR * a P *. Você não deve usar static_cast para lançar objetos reais para o seu super-classe por causa de "fatiar". Por exemplo. Se você converter um objeto de PW a P o material extra em PW será "cortado" off. Você também não pode usar static_cast para elenco de P * para PW *. Se você realmente tem que fazer isso, use dynamic_cast, que irá verificar em tempo de execução se o objeto é realmente da sub-classe certa, e dar-lhe um erro em tempo de execução se não é.

Eu não tenho certeza do que você quer dizer exatamente, mas urso comigo.

Eles meio que já são. Basta ligar tudo P e você pode fingir PR e PW são P da. PR e PW ainda são diferentes embora.

Fazendo todos os três equivalentes resultaria em problemas com a Liskov princípio . Mas então por que você iria dar-lhes nomes diferentes se eles são realmente equivalente?

O segundo eo terceiro seria inválido porque é um upcast implícita - que é uma coisa perigosa em C ++. Isso ocorre porque a classe que você está fundição para tem mais funcionalidades do que a classe que está sendo atribuído, a menos que você lançá-lo explicitamente a si mesmo, o compilador C ++ irá lançar um erro (pelo menos deveria). Claro, isso é simplificar as coisas um pouco (você pode usar RTTI para certas coisas que podem se relacionam com o que você quer fazer com segurança sem invocar a ira de mau object'ness) - mas a simplicidade é sempre uma boa maneira de problemas abordagem <. / p>

Claro que, como declarou em algumas outras soluções, você pode contornar esse problema -. Mas acho que antes de tentar contornar o problema, você pode querer repensar o projeto

Uso uma referência ou um ponteiro para P em vez de um objecto:

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

Isso permitirá que um PW * ou um PR * até ser obrigado a C.P. No entanto, se você precisa ir de um P a um PW ou PR, você precisa usar dynamic_cast (p), que retornará o PW * versão do p, ou NULL de p não é um PW * (para exemplo, porque é um PR *). Dynamic_cast tem alguma sobrecarga, porém, e é melhor evitar, se possível (virtuals de uso).

Você também pode usar o operador typeid () para determinar o tipo de tempo de execução de um objeto, mas tem várias questões, incluindo que você deve incluir e que ele não consegue detectar derivação extra.

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