2008-09-26 30 views
28

¿Alguien sabe si es posible tener una definición de clase parcial en C++?Definición de clase parcial en C++?

Algo así como:

file1.h:

 
class Test { 
    public: 
     int test1(); 
}; 

file2.h:

 
class Test { 
    public: 
     int test2(); 
}; 

Para mí parece bastante útil para definining clases de plataformas múltiples que tienen funciones comunes entre ellos que son independientes de la plataforma porque la herencia es un costo a pagar que no es útil para clases multiplataforma.

Quiero decir que nunca tendrá dos instancias de especialización multiplataforma en tiempo de ejecución, solo en tiempo de compilación. La herencia puede ser útil para satisfacer sus necesidades de interfaz pública, pero después de eso no agregará nada útil en tiempo de ejecución, solo costos.

también tendrá que utilizar un #ifdef feo para utilizar la clase porque no se puede hacer una instancia de una clase abstracta:

 
class genericTest { 
    public: 
     int genericMethod(); 
}; 

Entonces digamos para Win32:

 
class win32Test: public genericTest { 
    public: 
     int win32Method(); 
}; 
y tal vez

:

 
class macTest: public genericTest { 
    public: 
     int macMethod(); 
}; 

Vamos a pensar que tanto win32Method() y macMethod() llama genericMethod() Y que tendrá que utilizar la clase como esta:

 
#ifdef _WIN32 
genericTest *test = new win32Test(); 
#elif MAC 
genericTest *test = new macTest(); 
#endif 

test->genericMethod(); 

Ahora, pensando desde hace tiempo la herencia sólo era útil para dar a ambos un genericMethod() que depende de la sola plataforma específica, pero usted tiene la costo de llamar a dos constructores por eso. También tiene #ifdef feo dispersos por el código.

Es por eso que estaba buscando clases parciales. Pude en tiempo de compilación definir el extremo parcial dependiente de la plataforma específica, por supuesto que en este ejemplo tonto todavía necesito un #ifdef feo dentro de genericMethod() pero hay otras formas de evitar eso.

+0

en primer lugar, las funciones no virtuales no incurren en "costos" y en segundo lugar, si el código no indica qué versión de la interfaz que está utilizando, trabajando en una versión romperá la otra versión, por ejemplo cuando pruebas-> win32Method(). Las clases específicas de plataforma no son lo mismo y merecen diferentes nombres. – Jamie

+0

Y puede usar un método de fábrica para minimizar #ifdefs. Y puede usar plantillas (http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern) para reducir aún más. – Jamie

+0

C++ es un lenguaje tan viejo y pobre (( –

Respuesta

32

Esto no es posible en C++, se le dará un error sobre la redefinición de clases ya definidos. Si desea compartir el comportamiento, considere la herencia.

17

herencia Try

Específicamente

class AllPlatforms { 
public: 
    int common(); 
}; 

y luego

class PlatformA : public AllPlatforms { 
public: 
    int specific(); 
}; 
0

Como está escrito, no es posible.

Es posible que desee buscar en los espacios de nombres. Puede agregar una función a un espacio de nombres en otro archivo. El problema con una clase es que cada .cpp necesita ver el diseño completo de la clase.

1

Nope.

Pero, es posible que desee buscar una técnica llamada "Clases de políticas".Básicamente, usted hace microclases (que no son útiles por sí mismos) y luego los pega en un momento posterior.

+0

) Genial, Wikipedia tiene un lindo ejemplo: https://en.wikipedia.org/wiki/Policy-based_design – Andrew

0

Declarar un cuerpo de clase dos veces probablemente generará un error de redefinición de tipo. Si estás buscando una solución alternativa. Sugiero # ifdef'ing, o usando un Abstract Base Class para ocultar detalles específicos de la plataforma.

2

O usa la herencia, como dijo Jamie, o #ifdef para compilar diferentes partes en diferentes plataformas.

1

Dado que los encabezados se insertan textualmente, uno de ellos podría omitir la "clase Prueba {" y "}" y estar #incluido en el medio del otro.

Lo he visto en código de producción, aunque Delphi no C++. En particular, me molestó porque rompió las funciones de navegación del código del IDE.

+0

Esto es muy equivocado, muy equivocado, pero una solución válida, creo :-) – kervin

3

Para mí, parece bastante útil para definir las clases multiplataforma que tienen funciones comunes entre ellas que son independientes de la plataforma.

Excepto que los desarrolladores han estado haciendo esto durante décadas sin esta 'característica'.

Creo que parcial fue creado porque Microsoft ha tenido, durante décadas también, una mala costumbre de generar código y entregárselo a los desarrolladores para que lo desarrollen y mantengan.

El código genérico es a menudo una pesadilla de mantenimiento. ¿Qué hábitos tiene ese marco completo generado por MFC cuando necesita superar su versión de MFC? ¿O cómo transfiere todo ese código en los archivos * .designer.cs cuando actualiza Visual Studio?

La mayoría de las otras plataformas se basan más en generar archivos de configuración que el usuario/desarrollador puede modificar. Esos, que tienen un vocabulario más limitado y no propensos a mezclarse con código no relacionado. Los archivos de configuración incluso se pueden insertar en el binario como un archivo de recursos si se considera necesario.

Nunca he visto 'parcial' utilizado en un lugar donde la herencia o un archivo de recursos de configuración no habría hecho un mejor trabajo.

+0

¿Qué sucede si tiene una clase de interfaz de plantilla desde la que hereda CRTP-fashion, con la que también se habla otra clase de plantilla con las mismas especializaciones, y las clases derivadas de la interfaz y que utilizan el idioma pImpl tienen implementaciones idénticas para todos funciones públicas, pero deben tener diferentes implementaciones específicas de plataforma para todas las funciones privadas? Las clases parciales serían más limpias aquí que incluir archivos Impl separados por encima de la implementación de interfaz pública basada en un modificador #ifdef. – orfdorf

0

Puede obtener algo así como clases parciales utilizando plantilla de especialización y la especialización parcial . Antes de invertir demasiado tiempo, verifique el soporte de su compilador. Los compiladores antiguos como MSC++ 6.0 no admitían la especialización parcial.

8

o usted podría intentar PIMPL

archivo de cabecera común:

class Test 
{ 
public: 
    ... 
    void common(); 
    ... 
private: 
    class TestImpl; 
    TestImpl* m_customImpl; 
}; 

A continuación, crear los archivos cpp que hacen las implementaciones personalizadas que son específicos de la plataforma.

+0

Necesita un puntero a TestImpl, no una instancia ! – coppro

+0

gracias por la captura – PiNoYBoY82

+5

FTW, PIMPL está parado f o el modismo "puntero a la implementación". –

3

¿Qué tal esto:

class WindowsFuncs { public: int f(); int winf(); }; 
class MacFuncs { public: int f(); int macf(); } 

class Funcs 
#ifdef Windows 
    : public WindowsFuncs 
#else 
    : public MacFuncs 
#endif 
{ 
public: 
    Funcs(); 
    int g(); 
}; 

Ahora Funcs es una clase conocida en tiempo de compilación, por lo que no hay gastos generales son causadas por las clases base abstractas o lo que sea.

8
#include will work as that is preprocessor stuff. 

class Foo 
{ 
#include "FooFile_Private.h" 
} 

//////// 

FooFile_Private.h: 

private: 
    void DoSg(); 
+0

Buena idea loca, me gusta :) –

12

No puede definir parcialmente las clases en C++.

Esta es una forma de obtener el efecto de "polimorfismo, donde solo hay una subclase", sin gastos indirectos y con un mínimo de #define o duplicación de código. Se llama dinámica simulada vinculante:

template <typename T> 
class genericTest { 
public: 
    void genericMethod() { 
     // do some generic things 
     std::cout << "Could be any platform, I dunno" << std::endl; 
     // base class can call a method in the child with static_cast 
     (static_cast<T*>(this))->doClassDependentThing(); 
    } 
}; 

#ifdef _WIN32 
    typedef Win32Test Test; 
#elif MAC 
    typedef MacTest Test; 
#endif 

Entonces fuera en algunas otras cabeceras que tendrá:

class Win32Test : public genericTest<Win32Test> { 
public: 
    void win32Method() { 
     // windows-specific stuff: 
     std::cout << "I'm in windows" << std::endl; 
     // we can call a method in the base class 
     genericMethod(); 
     // more windows-specific stuff... 
    } 
    void doClassDependentThing() { 
     std::cout << "Yep, definitely in windows" << std::endl; 
    } 
}; 

y

class MacTest : public genericTest<MacTest> { 
public: 
    void macMethod() { 
     // mac-specific stuff: 
     std::cout << "I'm in MacOS" << std::endl; 
     // we can call a method in the base class 
     genericMethod(); 
     // more mac-specific stuff... 
    } 
    void doClassDependentThing() { 
     std::cout << "Yep, definitely in MacOS" << std::endl; 
    } 
}; 

Esto le da polimorfismo adecuada en tiempo de compilación. genericTest no puede prácticamente invocar a doClassDependentThing de forma que le proporcione la versión de plataforma (casi como un método virtual), y cuando win32Method llama a genericMethod, por supuesto, obtiene la versión de clase base.

Esto no genera gastos indirectos asociados con las llamadas virtuales; obtiene el mismo rendimiento que si hubiera escrito dos grandes clases sin código compartido. Puede crear una sobrecarga de llamada no virtual en con (de) destrucción, pero si el con (de) constructor para genericTest está en línea, debería estar bien, y esa sobrecarga en ningún caso es peor que tener un método genericInit que es invocado por ambas plataformas.

El código de cliente solo crea instancias de prueba y puede llamar a métodos que están en generic test o en la versión correcta para la plataforma. Para ayudar con la seguridad de tipos de código que no se preocupa por la plataforma y no quiere hacer uso de las llamadas accidentalmente específicas de la plataforma, se puede, además, hacer:

#ifdef _WIN32 
    typedef genericTest<Win32Test> BaseTest; 
#elif MAC 
    typedef genericTest<MacTest> BaseTest; 
#endif 

Tienes que ser un poco cuidadoso usando Prueba base, pero no mucho más que siempre es el caso con las clases base en C++. Por ejemplo, no lo cortes con un valor de paso sin valor juzgado. Y no lo instaure directamente, porque si lo hace y llama a un método que termina intentando una llamada "falsa virtual", tiene problemas. Esto último se puede aplicar asegurando que todos los constructores de genericTest estén protegidos.

+0

Creo que tienes la misma línea de pensamiento, me gustó. –

+0

Debería poder encontrar más consejos y trucos buscando en "enlace dinámico simulado" o quizás "polimorfismo estático". No estoy seguro de haberlo usado alguna vez seriamente, ya que en la práctica es bastante raro que la sobrecarga de las llamadas virtuales realmente importe. Si alguna vez lo hace, estaré listo :-) –

0

Las clases parciales son buenas si desea ampliar las clases sin tocar los archivos originales. Por ejemplo, quiero extender la clase de plantilla de vector de std con ayudantes. ¿Por qué demonios tengo que crear una clase heredada como vectorEx y no solo agregar métodos a través de parcial?

+0

Si pudiera ampliarlo directamente, podría acceder a los miembros privados del vector, lo que hace que el código dependa de su implementación. Una razón más técnica es que la clase podría compilarse por separado y se rompería si cambiara su estructura. –

1

Esto no es posible en C++, le dará un error acerca de la redefinición de las ya definidas clases. Si desea compartir el comportamiento, considere la herencia.

Estoy de acuerdo en esto. Las clases parciales son construcciones extrañas que hacen que sea muy difícil mantenerlas después. Es difícil ubicar en qué clase parcial se declara cada miembro y es difícil evitar la redefinición o incluso la reimplementación de las características.

¿Desea extender el std :: vector, tiene que heredar de él. Esto se debe a varias razones. En primer lugar, cambia la responsabilidad de la clase y (¿correctamente?) Sus invariantes de clase. En segundo lugar, desde el punto de vista de la seguridad, esto debe evitarse. Considere una clase que controla la autenticación de usuario ...

partial class UserAuthentication { 
    private string user; 
    private string password; 
    public bool signon(string usr, string pwd); 
} 

partial class UserAuthentication { 
    private string getPassword() { return password; } 
} 

Una gran cantidad de otras razones podría ser mencionado ...

0

plataforma Let dependientes clases/funciones independientes y de la plataforma sean cada-otras clases de amigo/funciones .:)

Y sus identificadores de nombre separados permiten un control más fino sobre la instanciación, por lo que el acoplamiento es más flexible. La fundamentación de encapsulación de cortes parciales de OO es demasiado absoluta, mientras que las declaraciones de amigo necesarias apenas lo relajan lo suficiente como para facilitar la separación de paradigmas múltiples de aspectos específicos de plataforma de plataformas independientes de dominio específico.

2

manera sucia pero práctica está utilizando preprocesador #include:

Test.h:

#ifndef TEST_H 
#define TEST_H 

class Test 
{ 
public: 
    Test(void); 
    virtual ~Test(void); 

#include "Test_Partial_Win32.h" 
#include "Test_Partial_OSX.h" 

}; 

#endif // !TEST_H 

Test_Partial_OSX.h:

// This file should be included in Test.h only. 

#ifdef MAC 
    public: 
     int macMethod(); 
#endif // MAC 

Test_Partial_WIN32.h:

// This file should be included in Test.h only. 

#ifdef _WIN32 
    public: 
     int win32Method(); 
#endif // _WIN32 

Test.cpp:

// Implement common member function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

Test::Test(void) 
{ 
} 

Test::~Test(void) 
{ 
} 

Test_Partial_OSX.cpp:

// Implement OSX platform specific function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

#ifdef MAC 
int Test::macMethod() 
{ 
    return 0; 
} 
#endif // MAC 

Test_Partial_WIN32.cpp:

// Implement WIN32 platform specific function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

#ifdef _WIN32 
int Test::win32Method() 
{ 
    return 0; 
} 
#endif // _WIN32 
0

que he estado haciendo algo similar en mi motor de renderizado. Tengo una clase con plantilla IResource interfaz desde la que una variedad de recursos inherit (despojado por razones de brevedad):

template <typename TResource, typename TParams, typename TKey> 
class IResource 
{ 
public: 
    virtual TKey GetKey() const = 0; 
protected: 
    static shared_ptr<TResource> Create(const TParams& params) 
    { 
     return ResourceManager::GetInstance().Load(params); 
    } 
    virtual Status Initialize(const TParams& params, const TKey key, shared_ptr<Viewer> pViewer) = 0; 
}; 

La función estática Create una llamada a una clase ResourceManager con plantilla que es responsable de la carga, descarga, y las instancias de almacenamiento del tipo de recurso que administra con claves únicas, asegurando que las llamadas duplicadas se recuperen simplemente de la tienda, en lugar de volver a cargarlas como recursos separados.

template <typename TResource, typename TParams, typename TKey> 
class TResourceManager 
{ 
    sptr<TResource> Load(const TParams& params) { ... } 
}; 

Las clases de recursos de hormigón heredan de IResource utilizando el CRTP. Los ResourceManagers especializados para cada tipo de recurso se declaran como amigos de esas clases, de modo que la función Load de ResourceManager puede llamar a la función Initialize del recurso concreto. Uno de estos recursos es una clase de textura, que utiliza además un modismo pImpl para ocultar sus partes íntimas:

class Texture2D : public IResource<Texture2D , Params::Texture2D , Key::Texture2D > 
{ 
    typedef TResourceManager<Texture2D , Params::Texture2D , Key::Texture2D > ResourceManager; 
    friend class ResourceManager; 

public: 
    virtual Key::Texture2D GetKey() const override final; 
    void GetWidth() const; 
private: 
    virtual Status Initialize(const Params::Texture2D & params, const Key::Texture2D key, shared_ptr<Texture2D > pTexture) override final; 

    struct Impl; 
    unique_ptr<Impl> m; 
}; 

Gran parte de la implementación de nuestra clase de textura es independiente de la plataforma (como la función GetWidth si sólo devuelve un int almacenado en el Impl). Sin embargo, dependiendo de la API de gráficos a la que nos dirigimos (por ejemplo, Direct3D11 vs. OpenGL 4.3), algunos de los detalles de implementación pueden diferir. Una solución podría ser heredar de IResource una clase intermedia Texture2D que define la interfaz pública extendida para todas las texturas, y luego heredar una clase D3DTexture2D y OGLTexture2D de eso. El primer problema con esta solución es que requiere que los usuarios de su API estén constantemente atentos a la API de gráficos a la que se dirigen (pueden llamar al Create en ambas clases secundarias). Esto podría resolverse restringiendo el Create a la clase intermediaria Texture2D, que usa quizás un conmutador #ifdef para crear un objeto secundario D3D u OGL. Pero aún existe el segundo problema con esta solución, que es que el código independiente de la plataforma se duplicaría en ambos niños, lo que ocasionaría esfuerzos adicionales de mantenimiento.Podría intentar resolver este problema moviendo el código independiente de la plataforma a la clase intermedia, pero ¿qué ocurre si algunos de los datos de los miembros son utilizados tanto por el código específico de la plataforma como por el independiente de la plataforma? Los niños D3D/OGL no podrán acceder a los miembros de datos en Impl del intermediario, por lo que tendrían que moverlos de la Impl y al encabezado, junto con las dependencias que llevan, exponiendo a cualquier persona que incluya su encabezado a toda esa basura que no necesitan saber.

Las API deben ser fáciles de usar, correctas y difíciles de usar. Parte de ser fácil de usar es restringir la exposición del usuario a solo las partes de la API que debería usar. Esta solución lo abre para que se use fácilmente de forma incorrecta y agrega gastos generales de mantenimiento. Los usuarios solo deberían preocuparse por la API de gráficos a la que apuntan en un solo lugar, no en todos los sitios donde usan su API, y no deberían estar expuestos a sus dependencias internas. Esta situación demanda clases parciales, pero no están disponibles en C++. Por lo tanto, en su lugar, simplemente puede definir la estructura Impl en archivos de encabezado separados, uno para D3D y otro para OGL, y ponga un interruptor #ifdef en la parte superior del archivo Texture2D.cpp y defina el resto de la interfaz pública de forma universal. De esta forma, la interfaz pública tiene acceso a los datos privados que necesita, el único código duplicado son las declaraciones de los miembros de datos (la construcción aún se puede hacer en el constructor Texture2D que crea el Impl), sus dependencias privadas se mantienen privadas y los usuarios no tiene que preocuparse por nada, excepto utilizando el conjunto limitado de llamadas en la superficie expuesta de la API:

// D3DTexture2DImpl.h 
#include "Texture2D.h" 
struct Texture2D::Impl 
{ 
    /* insert D3D-specific stuff here */ 
}; 

// OGLTexture2DImpl.h 
#include "Texture2D.h" 
struct Texture2D::Impl 
{ 
    /* insert OGL-specific stuff here */ 
}; 

// Texture2D.cpp 
#include "Texture2D.h" 

#ifdef USING_D3D 
#include "D3DTexture2DImpl.h" 
#else 
#include "OGLTexture2DImpl.h" 
#endif 

Key::Texture2D Texture2D::GetKey() const 
{ 
    return m->key; 
} 
// etc... 
Cuestiones relacionadas