Para evitar duplicação não-trivial relacionada à const C ++, existem casos em que const_cast funcionaria, mas uma função const privada retornando non-const não funcionaria?
No item efetivo C ++ de Scott Meyers , 3, ele sugere que um const_cast combinado com uma conversão estática pode ser uma maneira eficaz e segura de evitar código duplicado, por exemplo,
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Meyers continua explicando que ter a função const chamar a função non-const é perigoso.
O código abaixo é um contra-exemplo mostrando:
- Ao contrário da sugestão de Meyers, às vezes o const_cast combinado com um elenco estático é perigoso
- às vezes, ter a função const chamar a não-const é menos perigoso
- às vezes nos dois sentidos, usando um const_cast, oculta erros de compilador potencialmente úteis
- evitar um const_cast e ter um membro privado const adicional retornando um não-const é outra opção
Alguma das estratégias const_cast de evitar duplicação de código é considerada uma boa prática? Você prefere a estratégia de método privado? Existem casos em que const_cast funcionaria, mas um método privado não? Existem outras opções (além da duplicação)?
Minha preocupação com as estratégias const_cast é que, mesmo que o código esteja correto quando gravado, mais tarde durante a manutenção, o código poderá ficar incorreto e o const_cast ocultará um erro útil do compilador. Parece que uma função privada comum é geralmente mais segura.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
Respostas:
Ao implementar funções-membro const e não-const que diferem apenas se o ptr / reference retornado é const, a melhor estratégia DRY é:
por exemplo
Vamos chamar isso de função const particular, retornando um padrão não const .
Essa é a melhor estratégia para evitar duplicações de maneira direta, enquanto ainda permite ao compilador executar verificações potencialmente úteis e relatar mensagens de erro relacionadas à const.
fonte
const
instância (a menos que a referência seja a algo declaradomutable
ou a menos que vocêconst_cast
use um mas, em ambos os casos, não há probkem para começar) ) Também eu não poderia encontrar qualquer coisa na "função const privada retornando padrão não-const" (se ele tinha a intenção de ser uma piada de chamá-lo padrão .... ele isnt engraçado;)Sim, você está certo: muitos programas C ++ que tentam corrigir a const estão violando totalmente o princípio DRY, e mesmo o membro privado que retorna a não const é uma complexidade um pouco demais para o conforto.
No entanto, você perde uma observação: a duplicação de código devido à correção da const só é um problema se você estiver dando acesso a outro código aos membros dos dados. Isso por si só viola o encapsulamento. Geralmente, esse tipo de duplicação de código ocorre principalmente em acessadores simples (afinal, você está entregando acesso a membros já existentes, o valor de retorno geralmente não é o resultado de um cálculo).
Minha experiência é que boas abstrações não tendem a incluir acessadores. Consequentemente, evito amplamente esse problema definindo funções de membro que realmente fazem algo, em vez de apenas fornecer acesso aos membros de dados; Eu tento modelar o comportamento em vez de dados. Minha principal intenção é realmente obter alguma abstração de minhas classes e de suas funções-membro individuais, em vez de apenas usar meus objetos como contêineres de dados. Mas esse estilo também é bem-sucedido em evitar toneladas de acessadores de linha única repetitivos const / não-const que são tão comuns na maioria dos códigos.
fonte