2010-02-02 18 views
48

Recientemente, uno de mis amigos me preguntó cómo evitar la herencia de clases en C++. Él quería que la compilación fallara.Prevenir la herencia de clases en C++

Lo estaba pensando y encontré 3 respuestas. No estoy seguro cuál es el mejor.

1) Constructor privada (s)

class CBase 
{ 

public: 

static CBase* CreateInstance() 
{ 
    CBase* b1 = new CBase(); 
    return b1; 
} 

private: 

CBase() { } 
CBase(CBase3) { } 
CBase& operator=(CBase&) { } 


}; 

2) Uso de clase base CSealed, ctor privado & herencia virtual

class CSealed 
{ 

private: 

CSealed() { 
} 

friend class CBase; 
}; 


class CBase : virtual CSealed 
{ 

public: 

CBase() { 
} 

}; 

3) El uso de una clase base CSealed, ctor protegido & herencia virtual

class CSealed 
{ 

protected: 

CSealed() { 
} 

}; 

class CBase : virtual CSealed 
{ 

public: 

CBase() { 
} 

}; 

Todos los métodos anteriores aseguran que la clase CBase no se puede heredar más. Mi pregunta es:

1) ¿Cuál es el mejor método? ¿Hay otros métodos disponibles?

2) El método 2 & 3 no funcionará a menos que la clase CSealed se herede virutalmente. Porqué es eso ? ¿Tiene algo que ver con vdisp ptr?

PS:

El programa anterior fue compilado en MS compilador de C++ (Visual Studio). referencia: http://www.codeguru.com/forum/archive/index.php/t-321146.html

Respuesta

-1

Una solución más:

template < class T > 
class SealedBase 
{ 
protected: 
    SealedBase() 
    { 
    } 
}; 

#define Sealed(_CLASS_NAME_) private virtual SealedBase<_CLASS_NAME_> 


#include "Sealed.h" 

class SomeClass : Sealed(Penguin) 
{ 
}; 
+3

Agregar una macro a la mezcla no hace de esto una solución nueva. –

11

usted está pasando por contorsiones para evitar más subclases. ¿Por qué? Documente el hecho de que la clase no es extensible y haga que el dtor no sea virtual. En el espíritu de c, si alguien realmente quiere ignorar la forma en que pretendía que se usara, ¿por qué detenerlos? (Nunca vi el punto de final clases/métodos en Java tampoco).

//Note: this class is not designed to be extended. (Hence the non-virtual dtor) 
struct DontExtened 
{ 
    DontExtened(); 
    /*NOT VIRTUAL*/ 
    ~DontExtened(); 
    ... 
}; 
+10

Creo que el punto en Java es que el compilador JIT puede optimizar las llamadas a los métodos virtuales si la clase es final – Manuel

+0

@Manuel. Entiendo que a JVM le puede gustar, pero debería haber una manera fácil de deshacer eso sin cambiar la fuente. '@ReallyOverride?' – KitsuneYMG

+1

Eso se desvía un poco. En Java, la palabra clave 'final' tiene sentido porque todas las funciones son virtuales por defecto, por lo que hay mucho que ganar al permitir que el compilador JIT realice estas optimizaciones. En C++, no habría nada que ganar de un mecanismo similar para evitar la creación de subclases, que es la razón por la cual el lenguaje no proporciona un metanismo para hacerlo. – jalf

5

1) es una cuestión de gusto. Si lo veo correctamente, sus soluciones segunda y tercera más elegantes mueven el error en ciertas circunstancias desde el tiempo del enlace hasta el tiempo de compilación, que en general debería ser mejor.

2) La herencia virtual es necesaria para forzar la responsabilidad de inicializar la clase base (virtual) a la clase más derivada desde donde el ctor de la clase base ya no es accesible.

9

No puede evitar la herencia (antes de la palabra clave final de C++ 11): solo puede evitar la instanciación de clases heredadas. En otras palabras, no hay manera de impedir:

class A { ... }; 

class B : public A { ... }; 

Lo mejor que puede hacer es evitar que los objetos de tipo B de ser instanciado. Siendo ese el caso, sugiero que tome el consejo de kts y documente el hecho de que A (o lo que sea) no está destinado a ser usado para la herencia, le da un destructor no virtual y no tiene otras funciones virtuales, y déjelo así.

+1

+1: No puede evitar que alguien elija usar la herencia sobre la composición, aunque nosotros (y el resto del universo) podamos estar en desacuerdo. Documentarlo –

+5

Tenga en cuenta que en C++ 11 puede evitar fácilmente la herencia. –

1

Si puede, iré por la primera opción (constructor privado).La razón es que casi cualquier programador de C++ experimentado lo verá de un vistazo y podrá reconocer que está tratando de evitar la creación de subclases.

Puede haber otros métodos más complicados para evitar la creación de subclases, pero en este caso cuanto más simple, mejor.

4

Para responder a su pregunta, no puede heredar de CBase porque en la herencia virtual una clase derivada necesitaría tener acceso directo a la clase de la que se heredaba virtualmente. En este caso, una clase que se derivaría de CBase necesitaría tener acceso directo a CSealed que no puede, ya que el constructor es privado.

Aunque no veo la utilidad de todo esto (es decir: la herencia parar) se puede generalizar el uso de plantillas (no creo que se compila en todos los compiladores, pero lo hace con MSVC)

template<class T> 
class CSealed 
{ 
    friend T; // Don't do friend class T because it won't compile 
    CSealed() {} 
}; 

class CBase : private virtual CSealed<CBase> 
{ 
}; 
+1

Tiene que ser de clase CBase: privado virtual CSealed . De lo contrario, CBase se puede derivar. – Jagannath

54

a partir del 11 C++, puede agregar la palabra clave final a su clase, por ejemplo

class CBase final 
{ 
... 

la principal razón que puedo ver por querer hacer esto (y la razón por la que fue a buscar a esta pregunta) es marcar una clase no subclassable para que pueda usar de forma segura un destructor no virtual y evitar un vtable por completo.

+2

Hay otra buena razón y eso impide que las clases derivadas rompan el contrato de las clases inmutables. –

+0

@Nemanja Boric que se aplicaría a cualquier subclase y cualquier contrato, no solo la mutabilidad. Cualquier subclase puede potencialmente romper cualquier contrato implícito de la clase; esa no es realmente una buena razón para rechazar todas las subclases. Para un objeto inmutable, ¿qué ocurre si desea agregar un valor derivado, por ejemplo, FullName() de los métodos FirstName() y LastName(), o quizás una función hash específica? –

0
class myclass; 

    class my_lock { 
     friend class myclass; 
    private: 
     my_lock() {} 
     my_lock(const my_lock&) {} 
    }; 

    class myclass : public virtual my_lock { 
     // ... 
    public: 
     myclass(); 
     myclass(char*); 
     // ... 
    }; 

    myclass m; 

    class Der : public myclass { }; 

    Der dd; // error Der::dd() cannot access 
      // my_lock::my_lock(): private member 

Lo encontré aquí para dar crédito. Estoy publicar aquí sólo otras personas puedan acceder fácilmente a http://www.devx.com/tips/Tip/38482

0

Para más detalles sobre Francis' answer: si la clase Bottom deriva de la clase Middle, que hereda prácticamente de la clase Top, es que la clase más derivada (Bottom) que es responsable de la construcción la clase base virtualmente heredada (Top). De lo contrario, en el escenario de herencia múltiple/diamante de la muerte (donde la herencia virtual se usa de forma clásica), el compilador no sabría cuál de las dos clases "intermedias" debería construir la clase base única. Por lo tanto, 'llamada al constructor de s al Top' El Middle constructor s se ignora cuando Middle se está construyendo desde Bottom:

class Top { 
    public: 
     Top() {} 
} 

class Middle: virtual public Top { 
    public: 
     Middle(): Top() {} // Top() is ignored if Middle constructed through Bottom() 
} 

class Bottom: public Middle { 
    public: 
     Bottom(): Middle(), Top() {} 
} 

Así, en el enfoque 2) o 3) en su pregunta, Bottom() no se puede llamar Top() porque se hereda de forma privada (de forma predeterminada, como en el código, pero vale la pena hacerlo explícito) en Middle y, por lo tanto, no está visible en Bottom. (source)