c++ - studio - Cómo generar un error/advertencia del compilador cuando se corta el objeto
solidity español (8)
Quiero saber si es posible dejar que el compilador emita una advertencia / error para el código de la siguiente manera:
Nota:
1. Sí, es un mal estilo de programación y deberíamos evitar tales casos, pero estamos tratando con código heredado y el compilador de esperanza puede ayudar a identificar esos casos para nosotros.
2. Prefiero una opción de compilación (VC ++) para deshabilitar o habilitar el corte de objetos, si hay alguno.
class Base{};
class Derived: public Base{};
void Func(Base)
{
}
//void Func(Derived)
//{
//
//}
//main
Func(Derived());
Aquí si comento la segunda función, se llamaría a la primera función, y el compilador (tanto VC ++ como Gcc) se siente cómodo con eso.
¿Es C ++ estándar? y ¿puedo pedirle al compilador (VC ++) que me avise cuando coincida con dicho código?
¡¡¡Muchas gracias!!!
Editar:
¡Muchas gracias por su ayuda!
No puedo encontrar una opción de compilación para dar un error / advertencia: incluso publiqué esto en el foro de MSDN para el compilador de VC ++ sin respuesta. Por lo tanto, me temo que ni gcc ni vc ++ implementaron esta característica.
Así que agrega el constructor que tome las clases derivadas ya que paramter sería la mejor solución por el momento.
Editar
He enviado un feedbak a MS y espero que lo solucionen pronto:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=421579
-Baiyan
Esto se conoce comúnmente como Object Slicing y es un problema suficientemente conocido como para tener su propio artículo de Wikipedia (aunque es solo una breve descripción del problema).
Creo que he usado un compilador que tenía una advertencia que podría habilitar para detectar y advertir sobre esto. Sin embargo, no recuerdo cuál fue.
La mejor forma de combatir este problema es seguir la recomendación de Scott Meyer (ver C ++ efectivo ) de tener clases concretas en los nodos de hoja de su árbol de herencia y asegurarse de que las clases que no son hojas sean abstractas al tener al menos una función virtual pura ( el destructor, si nada más).
Es sorprendente la frecuencia con que este enfoque también ayuda a aclarar el diseño de otras maneras. El esfuerzo de aislar una interfaz abstracta común suele ser un esfuerzo de diseño que vale la pena en cualquier caso.
Editar
Aunque originalmente no lo dejé claro, mi respuesta proviene del hecho de que no es posible advertir con precisión sobre el corte de objetos en tiempo de compilación y por esta razón puede llevar a una falsa sensación de seguridad si tiene una aserción en tiempo de compilación , o una advertencia de compilación habilitada. Si necesita averiguar sobre las instancias de corte de objetos y necesita corregirlas, implica que tiene el deseo y la capacidad de cambiar el código heredado. Si este es el caso, entonces creo que debería considerar seriamente refactorizar la jerarquía de clases como una forma de hacer que el código sea más robusto.
Mi razonamiento es esto.
Considere algún código de biblioteca que defina una clase Concrete1 y la use en la inferface de esta función.
void do_something( const Concrete1& c );
Pasar el tipo ser referencia es para la eficiencia y es, en general, una buena idea. Si la biblioteca considera que Concrete1 es un tipo de valor, la implementación puede decidir hacer una copia del parámetro de entrada.
void do_something( const Concrete1& c )
{
// ...
some_storage.push_back( c );
// ...
}
Si el tipo de objeto de la referencia pasada es, de hecho, Concrete1
y no otro tipo derivado, este código es correcto, no se realiza ningún corte. Una advertencia general sobre esta push_back
función push_back
podría producir solo falsos positivos y muy probablemente no sería útil.
Considere algún código de cliente que derive Concrete2
de Concrete1
y lo pase a otra función.
void do_something_else( const Concrete1& c );
Como el parámetro se toma por referencia, aquí no se produce ningún corte en el parámetro que se va a verificar, por lo que no sería correcto advertir aquí sobre el corte, ya que puede ocurrir que no se produzca un corte. Pasar de un tipo derivado a una función que toma una referencia o un puntero es una forma común y útil de aprovechar los tipos polimórficos, por lo que advertir o rechazar esto parecería contraproducente.
Entonces, ¿dónde hay error? Bueno, el "error" está pasando en una referencia a algo que se deriva de una clase que luego es tratada como si fuera un tipo de valor por la función llamada.
En general, no hay forma de generar una advertencia de tiempo de compilación consistentemente útil contra el corte de objetos y esta es la razón por la que la mejor defensa, donde sea posible, es eliminar el problema por diseño.
No es realmente una solución a su problema inmediato, pero ...
La mayoría de las funciones que toman objetos de clase / estructura como parámetros deben declarar que los parámetros son del tipo "const X &" o "X &", a menos que tengan una muy buena razón para no hacerlo.
Si siempre hace esto, el corte de objetos nunca será un problema (¡las referencias no se cortan!).
Si puede modificar la clase base, puede hacer algo como:
class Base
{
public:
// not implemented will cause a link error
Base(const Derived &d);
const Base &operator=(const Derived &rhs);
};
Dependiendo de su compilador, debe obtener la unidad de traducción, y tal vez la función donde está ocurriendo el corte.
Sugiero agregar un constructor a su clase base que toma una referencia constante a la clase derivada explícitamente (con una declaración directa). En mi aplicación de prueba simple, se llama a este constructor en el caso del corte. Por lo menos, podría obtener una aserción en tiempo de ejecución, y probablemente podría obtener una aserción en tiempo de compilación con un uso inteligente de las plantillas (por ejemplo: instanciar una plantilla de una manera que genere una aserción en tiempo de compilación en ese constructor). También puede haber formas específicas de compilación para obtener advertencias o errores de tiempo de compilación cuando llama a funciones explícitas; por ejemplo, puede usar "__declspec (en desuso)" para el "constructor de sectores" en Visual Studio para obtener una advertencia en tiempo de compilación, al menos en el caso de llamada de función.
Entonces en su ejemplo, el código se vería así (para Visual Studio):
class Base { ...
__declspec(deprecated) Base( const Derived& oOther )
{
// Static assert here if possible...
}
...
Esto funciona en mi prueba (advertencia en tiempo de compilación). Tenga en cuenta que no resuelve el caso de copia, pero un operador de asignación construido de forma similar debería hacer el truco allí.
Espero que esto ayude. :)
class Derived: public Base{};
Usted dice que Derived IS es una Base, por lo que debería funcionar en cualquier función que tome una base. Si esto es un problema real, tal vez la herencia no es lo que realmente quieres usar.
Modifiqué tu código ligeramente:
class Base{
public:
Base() {}
explicit Base(const Base &) {}
};
class Derived: public Base {};
void Func(Base)
{
}
//void Func(Derived)
//{
//
//}
//main
int main() {
Func(Derived());
}
La palabra clave explícita se asegurará de que el constructor no se utilice como un operador de conversión implícito; debe llamarlo explícitamente cuando desee usarlo.
Como una variación de la respuesta de Andrew Khosravian , sugeriré el uso de constructores de copia con plantilla y operadores de asignación. De esta forma, no es necesario que conozca todas las clases derivadas de una clase base determinada para salvaguardar esa clase base de la segmentación:
class Base
{
private: // To force a compile error for non-friends (thanks bk1e)
// Not implemented, so will cause a link error for friends
template<typename T> Base(T const& d);
template<typename T> Base const& operator=(T const& rhs);
public:
// You now need to provide a copy ctor and assignment operator for Base
Base(Base const& d) { /* Initialise *this from d */ }
Base const& operator=(Base const& rhs) { /* Copy d to *this */ }
};
Aunque esto reduce la cantidad de trabajo necesario, con este enfoque todavía necesita jugar con cada clase base para salvaguardarlo. Además, causará problemas si hay conversiones legítimas de Base
a SomeOtherClass
que emplean un operator Base()
miembro de SomeOtherClass
. (En ese caso, se puede usar una solución más elaborada que incluya boost::disable_if<is_same<T, SomeOtherClass> >
). En cualquier caso, debe eliminar este código una vez que haya identificado todas las instancias de corte de objetos.
Para los implementadores de compiladores del mundo: ¡Las pruebas para el corte de objetos definitivamente son algo que valdría la pena al crear advertencias (opcionales)! No puedo pensar en una instancia en la que sería un comportamiento deseado, y se ve muy a menudo en el código C ++ para novatos.
[EDITAR 27/3/2015:] Como señaló Matt McNab, en realidad no es necesario declarar explícitamente el constructor de copias y el operador de asignación como hice anteriormente, ya que el compilador todavía los declarará implícitamente. En el estándar C ++ de 2003, esto se menciona explícitamente en la nota al pie 106 bajo 12.8 / 2:
Debido a que un constructor de plantilla nunca es un constructor de copia, la presencia de dicha plantilla no suprime la declaración implícita de un constructor de copia. Los constructores de plantilla participan en la resolución de sobrecarga con otros constructores, incluidos los constructores de copia, y se puede usar un constructor de plantilla para copiar un objeto si proporciona una mejor coincidencia que otros constructores.