En mi código, me acabo de dar cuenta de que a menudo necesito comprobar nullptr, aunque nullptr no debería ser posible (de acuerdo con los requisitos especificados).Evitar punteros nulos y mantener el polimorfismo
Sin embargo, nullptr aún puede ocurrir ya que otras personas podrían enviar un nullptr creyendo que esto está bien (lamentablemente no todo el mundo lee/escribe la especificación), y este defecto no puede detectarse a menos que el problema se desencadene durante la prueba (y la cobertura de prueba alta es costosa). Por lo tanto, podría dar lugar a muchos errores posteriores a la publicación informados por los clientes.
p. Ej.
class data
{
virtual void foo() = 0;
};
class data_a : public data
{
public:
virtual void foo(){}
};
class data_b : public data
{
public:
virtual void foo(){}
};
void foo(const std::shared_ptr<data>& data)
{
if(data == nullptr) // good idea to check before use, performance and forgetting check might be a problem?
return;
data->foo();
}
Normalmente, simplemente usaría value-types y lo pasaría por referencia y copia. Sin embargo, en algunos casos necesito polimorfismo que requiere punteros o referencias.
Así que he comenzado a utilizar el siguiente "polimorfismo de tiempo de compilación".
class data_a
{
public:
void foo(){}
private:
struct implementation;
std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};
class data_b
{
public:
void foo(){}
private:
struct implementation;
std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};
class data
{
public:
data(const data_a& x) : data_(x){} // implicit conversion
data(const data_b& x) : data_(x){} // implicit conversion
void foo()
{
boost::apply(foo_visitor(), data_);
}
private:
struct foo_visitor : public boost::static_visitor<void>
{
template<typename T>
void operator()(T& x){ x.foo(); }
};
boost::variant<data_a, data_b> data_;
}
void foo(const data& data)
{
data.foo();
}
¿Alguien más piensa que es una buena idea, cuando es práctico? ¿O me estoy perdiendo algo? ¿Hay algún problema potencial con esta práctica?
EDIT:
El "problema" con el uso de las referencias es que no puede mover la propiedad de una referencia (por ejemplo, devolver un objeto).
data& create_data() { data_a temp; return temp; } // ouch... cannot return temp;
El problema con referencias rvalue (polimorfismo funciona con referencias rvalue?) Se convierte entonces en que no se puede compartir la propiedad.
data&& create_data() { return std::move(my_data_); } // bye bye data
Un puntero "seguro" basado en shared_ptr hace sonar como una buena idea, pero todavía me gustaría una solución en la que no se hace cumplir nullness en tiempo de compilación, tal vez no sea posible.
Debe volver a escribir el código en la publicación. En algunos lugares está mezclando punteros y referencias, los punteros pueden ser nulos, y eliminarlos de referencias utiliza el operador '->'; las referencias no pueden ser nulas, y su desreferenciación utiliza el operador '.'. –
Hm ... No puedo ver el problema. ¿Te importaría señalar dónde está? – ronag
Otras cosas pequeñas: una vez que define * any * constructor, el compilador no generará el constructor predeterminado (no es necesario definirlo como privado), y si realmente desea deshabilitar un constructor --de nuevo, no requerido para el constructor predeterminado-- - es mejor declararlo privado pero * no * definirlo. Ser privado atrapará el uso de otras clases en el momento de la compilación, no definirlas atrapará usos dentro de su propia clase (o amigos) en el momento del enlace. –