¿Operador de asignación de copia predeterminado con miembro de referencia? [duplicar]

Aug 21 2020

Estoy trabajando en una aplicación heredada híbrida (C ++ / C #) en Visual Studio 2017, para la cual actualmente estoy tratando de agregar registros a una clase existente para ayudar a diagnosticar otro problema. La clase en cuestión es bastante sencilla:

class CAnExampleClass : CAnExampleBaseClass
{
public:
    CAnExampleClass();
    virtual ~CAnExampleClass(void);

    // dozens of public methods
private:
    // a few private methods
public:
    // dozens of public member variables, all intrinsic types or custom types with copy constructors or arrays thereof
private:
    class AnExampleChildImpl;
    mutable std::shared_ptr<AnExampleChildImpl> _pImpl;
    // a couple of friend classes
    bool _anExampleFlag;
public:
    static const int NumberOfItems = 15;
}

En la implementación, el constructor inicializa el miembro privado en la lista de inicialización así:

CAnExampleClass::CAnExampleClass()
    : _anExampleFlag(false)

Siguiendo el patrón establecido en otra parte de la aplicación, agregué un miembro a la clase para el registrador:

private:
    ILog& _log;

E inicializó este miembro en la lista de inicialización del constructor:

CAnExampleClass::CAnExampleClass()
    : _anExampleFlag(false), _log(CMyLog::GetLogger("log_name"))

El registrador es bastante simple:

class ILog
{
    ...
}
...
class CMyLog
{
public:
    ...
    static ILog& GetLogger(const char *loggerName);
    ...
}

Sin embargo, ahora el compilador informa un error:

Error   C2280   'CAnExampleClass &CAnExampleClass::operator =(const CAnExampleClass &)': attempting to reference a deleted function ...

Resulta que hay bastantes puntos en toda la aplicación en los que el código se basa en el operador de asignación de copia predeterminado para copiar una instancia existente de esta clase en una nueva instancia. También sucede que los otros casos en los que ya se ha utilizado el registrador es donde no hay intentos de copiar la clase utilizando el operador de asignación de copia.

Mi pregunta es, por lo tanto, ¿hay alguna alternativa para definir un operador de asignación de copia personalizado CAnExampleClassy copiar cada miembro (público) y matriz en la nueva instancia? Me parece una exageración que esto sea necesario cuando el miembro de referencia introducido se inicializa en la lista de inicialización del constructor: siendo ese el caso, ¿qué impide que el operador de asignación de copia predeterminado funcione como antes? ¿Por qué necesito definir una implementación personalizada del operador cuando hará, esencialmente, exactamente lo mismo que la implementación predeterminada, ya que la lista de inicialización se ocupa del miembro de referencia? ¿Hay alguna manera de que el operador de asignación de copia predeterminado ignore el miembro de referencia o de alguna manera invoque la lógica de copia predeterminada de una implementación de operador personalizada?

Modificar el código fuera de esta clase para tratar este problema no es una opción realista (la aplicación es demasiado grande, vieja, difícil de manejar, etc.) Idealmente, me gustaría evitar la opción de implementación del operador de asignación personalizada si hay una forma más simple, ya (obviamente) evita tener que volver a crear manualmente todas esas asignaciones de miembros, pero también evita la posibilidad de que si otro desarrollador del equipo agrega más tarde un miembro a esta clase, sin darse cuenta, se olvide de actualizar el operador de asignación de copia y terminemos con algún extraño error lógico en el código que depende de ese operador.

Respuestas

bitmask Aug 21 2020 at 12:41

¿Qué impide que el operador de asignación de copia predeterminado funcione como antes?

Presentó a un miembro CAnExampleClassde tipo ILog&. Las referencias no se pueden volver a cerrar. Una vez inicializado, nunca puede cambiar. Incluso si, debido a su patrón singleton, las instancias de ILogsiempre serán las mismas, tener un miembro de referencia eliminará automáticamente las funciones del miembro de asignación predeterminada.

O implementa el operador de asignación para todas las clases que tienen un miembro de referencia, lo que no parece ser factible para usted o descarta la referencia sin procesar a favor de std::reference_wrapper:

std::reference_wrapper<ILog> _log; // <- reseatable reference

o simplemente elimina el miembro por completo y siempre llama CMyLog::GetLogger("log_name")cuando necesita el objeto de registro, tal vez encapsulándolo en una función de miembro privada estática:

static ILog& logger() {
  // this ONLY works if GetLogger always returns the same object
  return CMyLog::GetLogger("log_name");
}