2011-04-24 19 views
25

En Visual Studio, existe __declspec(property) que crea propiedades similares a C#. Borland C++ ofrece la palabra clave __property con la misma funcionalidad exacta. En C++ 0x, se menciona una palabra clave implicit que podría expandirse para implementar la misma funcionalidad. Pero no entró en la especificación.Portabilidad de las propiedades nativas de C++

Estoy buscando un método portátil y relativamente limpio para declarar propiedades azucaradas sintácticamente que se compilarán en los últimos compiladores para Windows, OSX y Linux. No me preocupa la compatibilidad del compilador, solo un compilador por plataforma.

No busco alternativas a las propiedades que requieren paréntesis para obtener o establecer la propiedad, como métodos sobrecargados que separan los getters y los setters.

Aquí es un uso ideal que compila en Visual Studio 2010:

#define _property(_type, _name, _get, _put) __declspec(property(get=_get, put=_put)) _type _name 
#define _property_readonly(_type, _name, _get) __declspec(property(get=_get)) _type _name 

class Window 
{ 
public: 
    _property_readonly(void*, Handle, GetHandle); 
    _property(bool, Visible, GetVisible, SetVisible); 

    void* GetHandle(); 
    bool GetVisible(); 
    void SetVisible(bool); 
} 

void main() 
{ 
    Window MainWindow; 
    if (!MainWindow.Visible) 
     MainWindow.Visible = true; 
} 
+2

No hay palabra clave 'implicit' en C++ 0x. Sin embargo, no puedo acceder al enlace que publicaste (solicita un nombre de usuario/contraseña). –

+1

He actualizado el enlace. Parece que nunca llegó a la especificación C++ 0x. –

+11

Mi $ 0.02 es eso claramente si está apuntando a la portabilidad, simplemente manténgase alejado de estas extensiones. Período. – sehe

Respuesta

25

Esto es algo similar a lo que están pidiendo y es (espero) C++ estándar ...

#include <iostream> 

template<typename C, typename T, T (C::*getter)(), void (C::*setter)(const T&)> 
struct Property 
{ 
    C *instance; 

    Property(C *instance) 
     : instance(instance) 
    { 
    } 

    operator T() const 
    { 
     return (instance->*getter)(); 
    } 

    Property& operator=(const T& value) 
    { 
     (instance->*setter)(value); 
     return *this; 
    } 

    template<typename C2, typename T2, 
      T2 (C2::*getter2)(), void (C2::*setter2)(const T2&)> 
    Property& operator=(const Property<C2, T2, getter2, setter2>& other) 
    { 
     return *this = (other.instance->*getter2)(); 
    } 

    Property& operator=(const Property& other) 
    { 
     return *this = (other.instance->*getter)(); 
    } 
}; 

////////////////////////////////////////////////////////////////////////// 

struct Foo 
{ 
    int x_, y_; 

    void setX(const int& x) { x_ = x; std::cout << "x new value is " << x << "\n"; } 
    int getX() { std::cout << "reading x_\n"; return x_; } 

    void setY(const int& y) { y_ = y; std::cout << "y new value is " << y << "\n"; } 
    int getY() { std::cout << "reading y_\n"; return y_; } 

    Property<Foo, int, &Foo::getX, &Foo::setX> x; 
    Property<Foo, int, &Foo::getY, &Foo::setY> y; 

    Foo(int x0, int y0) 
     : x_(x0), y_(y0), x(this), y(this) 
    { 
    } 
}; 

int square(int x) 
{ 
    return x*x; 
} 

int main(int argc, const char *argv[]) 
{ 
    Foo foo(10, 20); 
    Foo foo2(100, 200); 
    int x = foo.x; std::cout << x << "\n"; 
    int y = foo.y; std::cout << y << "\n"; 
    foo.x = 42; std::cout << "assigned!\n"; 
    x = foo.x; std::cout << x << "\n"; 
    std::cout << "same instance prop/prop assign!\n"; 
    foo.x = foo.y; 
    std::cout << "different instances prop/prop assign\n"; 
    foo.x = foo2.x; 
    std::cout << "calling a function accepting an int parameter\n"; 
    std::cout << "square(" << foo.x << ") = " << square(foo.x) << "\n"; 
    return 0; 
} 

Como se puede ver en main el uso es transparente, siempre y cuando esté asignando valores de tipo T (aquí int) o implícitamente convertible a T en las propiedades y siempre que los vuelva a convertir a T valores en la lectura.

comportamiento será diferente por eso si usted, por ejemplo, pasar foo.x a una función de plantilla debido a que el tipo de foo.x no es int pero Property<Foo, int, ...> lugar.

También puede tener problemas con funciones que no son de plantilla ... llamar a una función que acepte un valor T funcionará bien; sin embargo, un parámetro T& es un problema porque básicamente la función solicita una variable para acceder directamente usando la dirección. Por la misma razón, no puede pasar, por supuesto, la dirección de una propiedad a una función que acepte un parámetro T*.

+0

Similar a [un meta-acceso simple] (http://www.kirit.com/C%2B%2B%20killed%20the%20get%20%26%20set%20accessors/A%20simple%20meta -accesor); las únicas diferencias son la elección de sobrecargar 'operator()' en lugar de 'operator =' y 'operator T', y (tipo de) discriminación entre los tipos POD y no POD. –

+1

El mayor inconveniente es que consume un puntero extra por propiedad, pero sin duda es aceptable por comodidad. Suponiendo optimizaciones en línea, tiene muy poca sobrecarga. Esto también es mucho más limpio que usar define con métodos propios por compilador y puedo tener getters y setters privados para mantener el IntelliSense limpio. Esto hace que la traducción del código dependiente de estructuras a clases basadas en eventos sea realmente sencilla. ¡Gracias! –

+0

Acabo de experimentar con la implementación de 'Property' anoche y me emocioné de inmediato (esperando las propiedades similares a C#). Luego traté de pasar la propiedad a un parámetro de función sin templado y presioné "no' int' pero 'Property ' "crux que mencionas al final de tu respuesta. Entonces, para cualquier otra persona interesada en probar esto, solo tenga en cuenta que estas propiedades no se pueden transmitir como si fueran su tipo subyacente. No son "nada más" que azúcar sintáctico para llamadas de función. Ni mas ni menos. –

3

Busco a un portátil y método relativamente limpia de declarar propiedades sintácticamente azucaradas que compilará en los últimos compiladores para Windows, OSX y Linux.

que usted describe las capacidades de tipo "meta-objeto", al igual que el tiempo de compilación o en tiempo de ejecución propiedades definidas, como las que pueden ser aplicadas distintas a través de "Java Beans" o "reflexión .NET", o de muchas maneras con lenguajes de scripting de alto nivel, como Python y Perl.

Por ejemplo, lo que estás describiendo (en tiempo de compilación y/o en tiempo de ejecución propiedades) se implementa en las bibliotecas Qt (C++) a través de la QMetaObject. Puede instanciarlo directamente, usarlo como un "miembro" en sus clases, o derivar de QObject para obtener "automáticamente" el comportamiento de los metaobjetos (y algunas otras cosas, como ayudas de "transmisión" y señales/ranuras de hilos cruzados)) Por supuesto, estos son bastante multiplataforma (por ejemplo, Win, Mac, Posix).

No soy un gran fan de la utilización __declspec(), excepto para uso específico de plataforma muy, tales como la exportación de tipos explícita a través de un "DLL de extensión de Microsoft" (que por lo general trato de evitar, si es posible). No creo que haya ninguna forma de hacer que ese uso sea "multiplataforma" (ya que ese uso particular es específico de las DLL de MS).

Del mismo modo, no sería muy difícil escribir su propia clase de tipo "MyMetaObject" que es esencialmente un "diccionario" o "hash" o "matriz asociativa", que utilizan sus objetos, y que se llena dinámicamente en tiempo de ejecución, incluso con sus tipos internos (como MyColor, MyTime, MyFilePath, etc.) Lo he hecho varias veces, y no necesita mucho trabajo, y puede funcionar con bastante elegancia. (El QMetaObject suele ser un poco más potente que estos enfoques simples, pero requiere el paso de compilación "moc", que es un paso muy poderoso para generar código de búsqueda rápida para sus propiedades y habilitar señales/ranuras).

Finalmente, está empezando a tocar ligeramente el dominio "Dynamic C++", lo que implica un uso más ligero, casi como un script, de la sintaxis de C++. Aquí hay una propuesta que profundiza un poco sobre este uso dinámico, en el que realiza un script con estas propiedades, sin necesidad de volver a compilar.(Esta propuesta en particular pasa a estar basado en el comportamiento QMetaObject tipo, pero hay otras propuestas con pensamientos similares de uso):

http://www.codeproject.com/KB/cpp/dynamic_cpp.aspx

Si google "Dynamic C++" o "C++ Scripting", es posible obtener algunas ideas más Hay algunos pensamientos maliciosamente astutos en algunas de esas cosas.

1

Me gusta la respuesta de 6502. Utiliza menos memoria y es más rápida que la solución que presentaré. Solo el mío tendrá un poco de azúcar sintáctico.

que quería ser capaz de wite algo como esto (con lenguaje PIMPL):

class A { 
private: 
    class FImpl; 
    FImpl* Impl; 

public: 
    A(); 
    ~A(); 

    Property<int> Count; 
    Property<int> Count2; 
    Property<UnicodeString> Str; 
    Property<UnicodeString> Readonly; 
}; 

Aquí viene el código finalizaron (estoy bastante seguro de que es conforme estándar):

template <typename value_t> 
class IProperty_Forward { 
public: 
    virtual ~IProperty_Forward() {} 
    virtual const value_t& Read() = 0; 
    virtual void Set(const value_t& value) = 0; 
}; 

template <typename value_t, typename owner_t, typename getter_t, typename setter_t> 
class TProperty_Forwarder: public IProperty_Forward<value_t> 
{ 
private: 
    owner_t* Owner; 
    getter_t Getter; 
    setter_t Setter; 
public: 
    TProperty_Forwarder(owner_t* owner, getter_t& getter, setter_t& setter) 
    :Owner(owner), Getter(getter), Setter(setter) 
    { } 

    const value_t& Read() 
     { return (Owner->*Getter)(); } 

    void Set(const value_t& value) 
     { (Owner->*Setter)(value); } 
}; 

template <typename value_t> 
class Property { 
private: 
    IProperty_Forward<value_t>* forward; 
public: 
    Property():forward(NULL) { } 

    template <typename owner_t, typename getter_t, typename setter_t> 
    Property(owner_t* owner, getter_t getter, setter_t setter) 
     { Init(owner, getter, setter); } 

    ~Property() 
     { delete forward; } 

    template <typename owner_t, typename getter_t, typename setter_t> 
    void Init(owner_t* owner, getter_t getter, setter_t setter) 
    { 
     forward = new TProperty_Forwarder<value_t, owner_t, getter_t, setter_t>(owner, getter, setter); 
    } 

    Property& operator=(const value_t& value) 
    { 
     forward->Set(value); 
     return *this; 
    } 

    const value_t* operator->() 
    { return &forward->Read(); } 

    const value_t& operator()() 
     { return forward->Read(); } 

    const value_t& operator()(const value_t& value) 
    { 
     forward->Set(value); 
     return forward->Read(); 
    } 

    operator const value_t&() 
     { return forward->Read(); } 
};  

Y algunos detalles de implementación:

class A::FImpl { 
    public: 
     FImpl():FCount(0),FCount2(0),FReadonly("Hello") { } 

     UnicodeString FReadonly; 
     const UnicodeString& getReadonly() 
      { return FReadonly; } 
     void setReadonly(const UnicodeString& s) 
      { } 

     int FCount; 
     int getCount() 
      { return FCount; } 
     void setCount(int s) 
      { FCount = s; } 

     int FCount2; 
     int getCount2() 
      { return FCount2; } 
     void setCount2(int s) 
      { FCount2 = s; } 

     UnicodeString FStr; 
     const UnicodeString& getStr() 
      { return FStr; } 
     void setStr(const UnicodeString& s) 
      { FStr = s; } 
}; 

A::A():Impl(new FImpl) 
{ 
    Count.Init(Impl, &FImpl::getCount, &FImpl::setCount); 
    Count2.Init(Impl, &FImpl::getCount2, &FImpl::setCount2); 
    Str.Init(Impl, &FImpl::getStr, &FImpl::setStr); 
    Readonly.Init(Impl, &FImpl::getReadonly, &FImpl::setReadonly); 
} 

A::~A() 
{ 
    delete Impl; 
} 

Estoy usando C++ Builder para cualquiera que se pregunte sobre el UnicodeS tring class. Espero que ayude a otros a experimentar con las propiedades de conformidad estándar de C++. El mecanismo básico es el mismo que el 6502, con las mismas limitaciones.

3

Clang ahora tiene el Microsoft __declspec(property...) completamente implementado y se optimiza maravillosamente. Así que usted puede utilizar propiedades en su C++ en todas las plataformas y se entremezclan en gcc basado o código C99 etc.

He estado usando durante más de un año, y esperó a que esto aparecerá universalmente desde hace más de cinco años.

Es una de las herramientas de C++ más poderosas para abstraer la estructura y el código de refactorización. Lo uso todo el tiempo para permitirme construir rápidamente una estructura y luego refactorizarla más adelante, según lo requiera el rendimiento o la reestructuración.

Tiene un valor incalculable y realmente no entiendo por qué los estándares de C++ no lo han adoptado hace mucho tiempo. Pero, una vez más, tienen mucho de la forma compleja e hinchada impulso de usar C++ y plantillas.

Clang es tan portátil en todas las plataformas ahora que tener esta característica es fantástico.

Desarrollo dentro (versión gratuita o de pago de) Visual Studio usando sonido metálico es casi transparente y se obtiene el increíble conjunto de herramientas de desarrollo de depuración que sólo hace que trabajar en otros conjuntos de herramientas y plataformas dolorosas en comparación.

Uso exclusivamente clang ahora para todo mi desarrollo de C++.

Cuestiones relacionadas