2009-12-10 22 views
10

Por supuesto, sé que la mejor respuesta es "no escriba su propio código multiplataforma, alguien ya ha hecho lo que necesita", pero lo estoy haciendo como un pasatiempo/aprendizaje ejercicio y no en ninguna capacidad de pago. Básicamente, estoy escribiendo una aplicación de consola pequeña en C++, y me gustaría hacerlo en varias plataformas, tratando cosas como archivos, sockets e hilos. OOP parece ser una gran manera de manejar esto, pero realmente no he encontrado un buen patrón para escribir clases que compartan la misma interfaz de plataforma cruzada.OOP multiplataforma en C++

El enfoque sencillo es simplemente planear metainterfaces, usarlas en todo el resto del programa y compilar la misma clase con diferentes archivos según la plataforma, pero creo que debe haber una mejor camino que es más elegante; al menos, algo que no confunde IntelliSense y su tipo sería bueno.

que he tomado un vistazo a algunas de las clases más pequeñas en la fuente de wxWidgets, y utilizan un enfoque que utiliza un miembro privado almacenamiento de datos para la clase, por ejemplo

class Foo 
{ 
    public: 
     Foo(); 

     void Bar(); 
    private: 
     FooData data; 
}; 

Luego, puede compilar este simplemente eligiendo diferentes archivos de implementación según la plataforma. Este enfoque me parece bastante torpe.

Otro enfoque que he considerado es escribir una interfaz y cambiar las clases heredadas de esa interfaz según la plataforma. Algo como esto:

class Foo 
{ 
    public: 
     virtual ~Foo() {}; 
     virtual void Bar() = 0; 
}; 

class Win32Foo 
{ 
    public: 
     Win32Foo(); 
     ~Win32Foo(); 
     void Bar(); 
}; 

Por supuesto, este tipo de tornillos hasta la creación de instancias real, ya que no sabe qué aplicación para crear un objeto de, pero eso se puede evitar mediante el uso de una función

Foo* CreateFoo(); 

y variando la implementación de la función según la plataforma en la que se esté ejecutando. Tampoco soy un gran admirador de esto, ya que todavía parece poco claro ensuciar el código con un montón de método de creación de instancias (y esto también sería inconsistente con el método de creación de objetos que no son de plataforma cruzada).

¿Cuál de estos dos enfoques es mejor? ¿Hay una mejor manera?

Editar: Para aclarar, mi pregunta no es "¿Cómo se escribe C++ multiplataforma?" Más bien, es "¿Cuál es el mejor método para abstraer el código multiplataforma utilizando clases en C++, conservando al mismo tiempo el mayor beneficio posible del sistema tipográfico?"

Respuesta

10

definir su interfaz, que reenvía a detail llamadas:

#include "detail/foo.hpp" 

struct foo 
{ 
    void some_thing(void) 
    { 
     detail::some_thing(); 
    } 
} 

Donde "detalle/foo.hpp" es algo así como:

namespace detail 
{ 
    void some_thing(void); 
} 

se podría entonces implementar esto en detail/win32/foo.cpp o detail/posix/foo.cpp, y dependiendo de la plataforma de su compilación para, compilar una o la otra.

La interfaz común simplemente reenvía las llamadas a las implementaciones específicas de la implementación. Esto es similar a cómo lo hace el impulso. Deberá consultar boost para obtener todos los detalles.

+1

Esa es una buena idea, y definitivamente voy a echar un vistazo a lo que Boost hace allí. Tengo curiosidad, ¿este patrón permite que el estado de la clase varíe entre las implementaciones? Por ejemplo, los identificadores de archivo Posix vs Win32 tienen un tipo diferente; si estuviera escribiendo un contenedor de archivos, las estructuras/clases de estado tendrían que ser diferentes. – ShZ

+2

Haría una clase 'file_handle', y simplemente rellenaría el handle en' void * ', por lo general. La implementación puede devolverlo a lo que necesita. Esto está garantizado seguro por el estándar. Este método también funciona bien si carga implementaciones dinámicamente, ya que simplemente reemplaza 'detail :: whatever' con' some_function_pointer', cargado desde la biblioteca. – GManNickG

+1

+1: siempre delegue en el sistema de compilación lo que es específico del tiempo de compilación –

15

Para implementar realmente la funcionalidad multiplataforma que requiere compatibilidad con el sistema operativo (como sockets), tendrá que escribir código que simplemente no compilará en ciertas plataformas. Eso significa que su diseño orientado a objetos deberá complementarse con directivas de preprocesador.

Pero ya que tendrá que usar el preprocesador de todos modos, es cuestionable si algo como un win32socket (por ejemplo) que hereda de una clase base socket es incluso necesario o útil. OO es generalmente útil cuando diferentes funciones se seleccionarán polimórficamente en tiempo de ejecución. Pero la funcionalidad multiplataforma suele ser más un problema de tiempo de compilación. (Por ejemplo, no sirve de nada llamar polimórficamente al win32socket::connect si esa función ni siquiera se compila en Linux). Por lo tanto, podría ser mejor simplemente crear una clase socket que se implemente de manera diferente según la plataforma utilizando las directivas de preprocesador.

+9

En realidad no es necesario para la basura su código con condicionales del preprocesador - sólo hay que poner el código específico de la plataforma en archivos separados y hacer la selección de los archivos make/scripts de creación. –

+1

Derecha; las directivas de preprocesador pueden ser algo tan simple como incluir diferentes archivos de encabezado dependiendo de la plataforma. –

+0

Absolutamente: mi pregunta era más acerca de cómo se debe hacer para crear las diferentes implementaciones para una clase de socket genérica. Disculpas por la ambigüedad allí. – ShZ

1

El segundo enfoque es mejor porque le permite crear funciones miembro que existirán solo en una de las plataformas. Luego, puede usar la clase abstracta en el código independiente de la plataforma y la clase concreta en el código específico de la plataforma.

Ejemplo:

class Foo 
{ 
    public: 
     virtual ~Foo() {}; 
     virtual void Bar() = 0; 
}; 
class Win32Foo : public Foo 
{ 
    public: 
     void Bar(); 
     void CloseWindow(); // Win32-specific function 
}; 

class Abc 
{ 
    public: 
     virtual ~Abc() {}; 
     virtual void Xyz() = 0; 
}; 
class Win32Abc : public Abc 
{ 
    public: 
     void Xyz() 
     { 
      // do something 
      // and Close the window 
      Win32Foo foo; 
      foo.CloseWindow(); 
     } 
}; 
2

Lo ideal sería concentrar las diferencias en el nivel más bajo posible.
Así que su código de nivel superior llama a Foo() y solo Foo() necesita cuidar internamente el sistema operativo al que está llamando.

ps. Echar un vistazo a 'impulso' que contiene una gran cantidad de cosas para manejar sistemas de archivos de red de plataforma cruzada, etc.

1

Delegando como en su primer ejemplo o GMan funciona bastante bien, especialmente si la parte específica de la plataforma necesitará muchos miembros específicos de la plataforma (por ejemplo, miembros de tipos que solo existen en una plataforma). Lo malo es que debes mantener dos clases.

Si la clase va a ser más simple, sin muchos miembros específicos de la plataforma, puede usar una declaración de clase común y escribir dos implementaciones en dos archivos .cc diferentes (más tal vez un tercero .cc para plataforma independiente implementaciones de métodos). Es posible que necesite #ifdefs en el archivo de encabezado para los pocos miembros o miembros específicos de la plataforma con tipos específicos de la plataforma. De esta manera es más un truco, pero puede ser más fácil para empezar. Siempre puede cambiar a delegados si se sale de control.

4

No necesita pasar OOP para ser multiplataforma. La plataforma cruzada es más un paradigma de arquitectura y diseño.

Sugiero que se aísle todo el código específico de la plataforma del código estándar, como la GUI de los sockets. Compare estas en las diferentes plataformas y escriba una capa o envoltorio genérico. Usa esta capa en tu código. Crear funciones de biblioteca para implementar la implementación específica de la plataforma de la capa genérica.

Las funciones específicas de la plataforma deben estar en bibliotecas o archivos separados. Su proceso de compilación debe usar las bibliotecas correctas para las plataformas específicas. No debe haber cambios en su código "estándar".

1

Como han notado otros, la herencia es una herramienta incorrecta para el trabajo. La decisión del tipo concreto para usar en su caso se toma en el momento de la compilación, y el polimorfismo clásico permite que la decisión se tome en tiempo de ejecución (es decir, como resultado de la acción de los usuarios).

2

Puede utilizar la especialización de plantilla para separar el código para diferentes plataformas.

enum platform {mac, lin, w32}; 

template<platform p = mac> 
struct MyClass 
{ 
// Platform dependent stuff for whatever platform you want to build for default here... 
}; 

template<> 
struct MyClass<lin> 
{ 
// Platform dependent stuff for Linux... 
}; 

template<> 
struct MyClass<w32> 
{ 
// Platform dependent stuff for Win32... 
}; 

int main (void) 
{ 
MyClass<> formac; 
MyClass<lin> forlin 
MyClass<w32> forw32; 
return 0; 
} 
+0

Encontré este 'modismo' aquí también: http://altdevblog.com/2011/06/27/platform-abstraction-with-cpp-templates / – Julien