Quando eu aprendi C ++ há muito tempo, enfatizou-me fortemente que parte do ponto do C ++ é que, assim como os loops têm "invariantes em loop", as classes também têm invariantes associados ao tempo de vida do objeto - coisas que devem ser verdadeiras enquanto o objeto estiver vivo. Coisas que devem ser estabelecidas pelos construtores e preservadas pelos métodos. O controle de acesso / encapsulamento existe para ajudá-lo a impor os invariantes. RAII é uma coisa que você pode fazer com essa ideia.
Desde o C ++ 11, agora temos a semântica de movimentação. Para uma classe que apóia a movimentação, a movimentação de um objeto não termina formalmente sua vida útil - a ação deve deixá-lo em um estado "válido".
Ao projetar uma classe, é uma prática inadequada projetá-la para que os invariantes da classe sejam preservados apenas até o ponto em que foram movidos? Ou está tudo bem se isso permitir que você vá mais rápido.
Para torná-lo concreto, suponha que eu tenha um tipo de recurso não copiável, mas móvel, da seguinte forma:
class opaque {
opaque(const opaque &) = delete;
public:
opaque(opaque &&);
...
void mysterious();
void mysterious(int);
void mysterious(std::vector<std::string>);
};
E, por qualquer motivo, preciso criar um invólucro copiável para esse objeto, para que possa ser usado, talvez em algum sistema de despacho existente.
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { o_->mysterious(); }
void operator()(int i) { o_->mysterious(i); }
void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};
Nesse copyable_opaque
objeto, uma invariante da classe estabelecida na construção é que o membro o_
sempre aponta para um objeto válido, pois não há um ctor padrão, e o único que não é um copiador garante isso. Todos os operator()
métodos assumem que esse invariante se mantém e preservam depois.
No entanto, se o objeto for movido, o_
ele apontará para nada. E depois desse ponto, chamar qualquer um dos métodos operator()
causará falha no UB / a.
Se o objeto nunca for movido, a invariante será preservada até a chamada do dtor.
Suponhamos que, hipoteticamente, eu escrevi essa classe e, meses depois, meu colega de trabalho imaginário tenha experimentado a UB porque, em alguma função complicada em que muitos desses objetos estavam sendo embaralhados por algum motivo, ele se mudou de uma dessas coisas e depois chamou uma das seus métodos. Claramente, a culpa é dele no final do dia, mas essa classe é "mal projetada?"
Pensamentos:
Geralmente, é uma má forma no C ++ criar objetos zumbis que explodem se você os tocar.
Se você não pode construir algum objeto, não pode estabelecer os invariantes, lance uma exceção do ctor. Se você não conseguir preservar os invariantes de algum método, sinalize um erro de alguma forma e faça a reversão. Isso deve ser diferente para objetos movidos de?É suficiente apenas documentar "depois que esse objeto foi movido, é ilegal (UB) fazer algo com ele além de destruí-lo" no cabeçalho?
É melhor afirmar continuamente que é válido em cada chamada de método?
Igual a:
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { assert(o_); o_->mysterious(); }
void operator()(int i) { assert(o_); o_->mysterious(i); }
void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};
As afirmações não melhoram substancialmente o comportamento e causam uma desaceleração. Se o seu projeto usa o esquema "release build / debug build", em vez de sempre executar com asserções, acho que isso é mais atraente, pois você não paga pelas verificações na versão. Se você realmente não tem compilações de depuração, isso parece pouco atraente.
- É melhor tornar a classe copiável, mas não móvel?
Isso também parece ruim e causa um impacto no desempenho, mas resolve o problema "invariável" de maneira direta.
Quais você consideraria as "melhores práticas" relevantes aqui?