O C ++ 11 tem propriedades no estilo C #?


93

Em C #, há um açúcar de sintaxe agradável para campos com getter e setter. Além disso, gosto das propriedades implementadas automaticamente, que me permitem escrever

public Foo foo { get; private set; }

Em C ++ eu tenho que escrever

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

Existe algum conceito desse tipo no C ++ 11, permitindo-me ter algum açúcar de sintaxe nisso?


62
Isso pode ser feito com algumas macros. foge de vergonha
Mankarse

7
@Eloff: Tornar tudo público SEMPRE é uma má ideia.
Kaiserludi

8
Esse conceito não existe! E você não precisa disso também: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) esta questão é bastante antiga b) Eu estava pedindo sintaxe sugar, que me permitiria me livrar dos parênteses c) embora o artigo apresente argumentos válidos contra a adaptação de propriedades, se C ++ 'precisa ou não precisa' de propriedades é muito subjetivo. C ++ é equivalente a uma máquina de turismo mesmo sem eles, mas isso não significa que ter esse açúcar de sintaxe tornaria o C ++ mais produtivo.
Radim Vansa

3
Definitivamente não.

Respostas:


87

Em C ++, você pode escrever seus próprios recursos. Aqui está um exemplo de implementação de propriedades usando classes sem nome. Artigo da Wikipedia

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

Você pode escrever seus próprios getters e setters no local e, se desejar acesso de membro da classe titular, poderá estender este código de exemplo.


1
Alguma ideia sobre como modificar este código para ainda ter uma variável de membro privado que Foo pode acessar internamente, enquanto a API pública apenas expõe a propriedade? Eu poderia, claro, apenas tornar Foo um amigo de alfa / beta, mas então eu ainda teria que escrever alpha.value, para acessar o valor, mas eu preferiria que acessar diretamente a variável de membro de dentro de Foo fosse mais parecido com acessando um membro do próprio Foo e não um membro de uma classe de propriedade aninhada especial.
Kaiserludi

1
@Kaiserludi Sim: neste caso, torne alpha e bravo privados. No Foo, você pode ler / escrever com as "propriedades" acima, mas fora do Foo, isso não seria mais possível. Para contornar isso, faça uma referência constante que seja pública. É acessível de fora, mas apenas para leitura, pois é uma referência constante. A única ressalva é que você precisará de outro nome para a referência const pública. Eu pessoalmente usaria _alphapara a variável privada e alphapara a referência.
Kapichu

1
@Kapichu: não é uma solução, por 2 motivos. 1) Em C #, getters / setters de propriedade são freqüentemente usados ​​para incorporar verificações de segurança que são forçadas para usuários públicos da classe, permitindo que as funções de membro acessem o valor diretamente. 2) as referências const não são gratuitas: dependendo do compilador / plataforma, elas serão ampliadas sizeof(Foo).
ceztko

@psx: por causa das limitações dessa abordagem, eu a evitaria e esperaria pela adição adequada ao padrão, se ele aparecer.
ceztko

@Kapichu: Mas alpha e bravo no código de exemplo são as propriedades. Gostaria de acessar diretamente a própria variável de dentro da implementação do Foo, sem a necessidade de usar uma propriedade, enquanto gostaria de apenas expor o acesso por meio de uma propriedade na API.
Kaiserludi

53

C ++ não tem isso integrado, você pode definir um modelo para imitar a funcionalidade das propriedades:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

Para definir uma propriedade :

Property<float> x;

Para implementar um getter / setter personalizado, basta herdar:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

Para definir uma propriedade somente leitura :

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

E para usá-lo na aulaOwner :

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

Você pode definir alguns dos itens acima em macros para torná-lo mais conciso.


Estou curioso para saber se isso compila para um recurso de custo zero, não sei se envolver cada membro de dados em uma instância de classe resultará no mesmo tipo de empacotamento de estrutura, por exemplo.
Dai

1
A lógica "getter / setter personalizado" poderia ser sintaticamente mais limpa com o uso de funções lambda, infelizmente você não pode definir um lambda fora de um contexto executável em C ++ (ainda!), Portanto, sem usar uma macro de pré-processador, você acaba com um código que é apenas tão complicados quanto getters / setters burros, o que é lamentável.
Dai

2
A "classe: ..." no último exemplo é interessante e está ausente dos outros exemplos. Ele cria a declaração de amigo necessária - sem introduzir um novo nome de classe.
Hans Olsson

Uma grande diferença entre essa resposta e a resposta de 19 de novembro de 2010 é que isso permite substituir o getter ou setter caso a caso. Dessa forma, pode-se verificar se a entrada está dentro do intervalo, ou enviar uma notificação de alteração para alterar ouvintes de evento, ou um local para travar um ponto de interrupção.
Eljay

Observe que provavelmentevirtual é desnecessário para a maioria dos casos de uso, uma vez que é improvável que uma propriedade seja usada polimorficamente.
Eljay

28

Não há nada na linguagem C ++ que funcione em todas as plataformas e compiladores.

Mas se você estiver disposto a quebrar a compatibilidade de plataforma cruzada e se comprometer com um compilador específico, você pode usar essa sintaxe, por exemplo, no Microsoft Visual C ++ ,

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}


18

Você pode emular getter e setter, até certo ponto por ter um membro do tipo dedicado e substituindo operator(type)e operator=por isso. Se é uma boa ideia é outra questão e irei à +1resposta de Kerrek SB para expressar minha opinião sobre isso :)


Você pode emular a chamada de um método ao atribuir ou ler por esse tipo, mas não consegue distinguir quem chama a operação de atribuição (para proibi-la se não for o proprietário do campo) - o que estou tentando fazer especificando um acesso diferente nível para getter e setter.
Radim Vansa

@Flavius: Basta adicionar um friendao proprietário do campo.
kennytm

17

Talvez dê uma olhada na classe de propriedade que montei nas últimas horas: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Ele permite que você tenha propriedades que se comportam assim:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

Bom, no entanto, embora isso permita acessadores definidos pelo usuário, não há o recurso getter público / setter privado que eu estava procurando.
Radim Vansa

17

Com C ++ 11, você pode definir um modelo de classe Property e usá-lo assim:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

E aqui está o template da classe Property.

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};

2
o que C::*significa Eu nunca vi nada parecido antes?
Rika de

1
É um ponteiro para uma função de membro não estática na classe C. Isso é semelhante a um ponteiro de função simples, mas para chamar a função de membro, você precisa fornecer um objeto no qual a função é chamada. Isso é feito com a linha itsObject->*itsSetter(theValue)do exemplo acima. Veja aqui uma descrição mais detalhada deste recurso.
Christoph Böhme

@Niceman, existe um exemplo de uso? Parece ser muito caro ter como membro. Também não é particularmente útil como membro estático.
Grim Fandango de

16

Como muitos outros já disseram, não há suporte embutido na linguagem. No entanto, se o seu objetivo é o compilador Microsoft C ++, você pode aproveitar as vantagens da extensão específica da Microsoft para propriedades documentadas aqui.

Este é o exemplo da página vinculada:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

12

Não, C ++ não tem conceito de propriedades. Embora possa ser difícil definir e chamar getThis () ou setThat (value), você está fazendo uma declaração ao consumidor desses métodos de que alguma funcionalidade pode ocorrer. Acessar campos em C ++, por outro lado, informa ao consumidor que nenhuma funcionalidade adicional ou inesperada ocorrerá. As propriedades tornariam isso menos óbvio, pois o acesso à propriedade à primeira vista parece reagir como um campo, mas na verdade reage como um método.

À parte, eu estava trabalhando em um aplicativo .NET (um CMS muito conhecido) tentando criar um sistema de associação do cliente. Devido à maneira como eles usaram propriedades para seus objetos de usuário, ações foram disparadas que eu não havia previsto, fazendo com que minhas implementações fossem executadas de maneiras bizarras, incluindo recursão infinita. Isso acontecia porque seus objetos de usuário faziam chamadas para a camada de acesso a dados ou algum sistema de cache global ao tentar acessar coisas simples como StreetAddress. Todo o seu sistema foi baseado no que eu chamaria de abuso de propriedades. Se eles tivessem usado métodos em vez de propriedades, acho que teria descoberto o que estava errado muito mais rapidamente. Se eles tivessem usado campos (ou pelo menos feito suas propriedades se comportassem mais como campos), acho que o sistema teria sido mais fácil de estender e manter.

[Editar] Mudei meus pensamentos. Tive um dia ruim e comecei a reclamar um pouco. Essa limpeza deve ser mais profissional.


11

Com base em https://stackoverflow.com/a/23109533/404734, aqui está uma versão com um getter público e um setter privado:

struct Foo
{
    class
    {
            int value;
            int& operator= (const int& i) { return value = i; }
            friend struct Foo;
        public:
            operator int() const { return value; }
    } alpha;
};

4

Esta não é exatamente uma propriedade, mas faz o que você quiser de maneira simples:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

Aqui, o grande X se comporta como public int X { get; private set; }na sintaxe C #. Se você deseja propriedades completas, fiz uma primeira tentativa para implementá-las aqui .


2
Isso não é uma boa ideia. Sempre que você fizer uma cópia de um objeto desta classe, a referência Xdo novo objeto ainda apontará para o membro do objeto antigo, porque ele é apenas copiado como um membro ponteiro. Isso é ruim por si só, mas quando o objeto antigo é excluído, há corrupção de memória em cima dele. Para fazer isso funcionar, você também teria que implementar seu próprio construtor de cópia, operador de atribuição e construtor de movimento.
toster

4

Você provavelmente sabe disso, mas eu simplesmente faria o seguinte:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

Essa abordagem é simples, não usa truques inteligentes e dá conta do recado!

O problema, porém, é que algumas pessoas não gostam de prefixar seus campos privados com um sublinhado e, portanto, não podem realmente usar essa abordagem, mas, felizmente, para aqueles que usam, é realmente simples. :)

Os prefixos get e set não adicionam clareza à sua API, mas os tornam mais detalhados e a razão pela qual eu não acho que eles adicionam informações úteis é porque quando alguém precisa usar uma API se a API faz sentido, ela provavelmente vai perceber o que é faz sem os prefixos.

Mais uma coisa, é fácil entender que essas são propriedades porque namenão é um verbo.

Na pior das hipóteses, se as APIs forem consistentes e a pessoa não perceber que name()é um acessador ename(value) um modificador, ela só terá que procurar uma vez na documentação para entender o padrão.

Por mais que eu ame C #, acho que C ++ não precisa de propriedades!


Seus mutadores fazem sentido se um usar foo(bar)(em vez do mais lento foo = bar), mas seus acessores não têm nada a ver com propriedades ...
Matthias

@Matthias Dizendo que não tem nada a ver com propriedades, não me diz nada, pode explicar? além disso, não tentei compará-los, mas se você precisar de um modificador e acessador, pode usar esta convenção.
Eyal Solnik

A questão é sobre o conceito de software de Propriedades. As propriedades podem ser usadas como se fossem membros de dados públicos (uso), mas, na verdade, são métodos especiais chamados acessadores (declaração). Sua solução se fixa em métodos (getters / setters comuns) para a declaração e o uso. Portanto, este não é, em primeiro lugar, definitivamente o uso que o OP está pedindo, mas sim alguma convenção de nomenclatura estranha e não convencional (e, portanto, em segundo lugar, também sem açúcar sintático).
Matthias

Como um efeito colateral menor, seu mutador surpreendentemente funciona como uma propriedade, uma vez que em C ++ é melhor inicializar com, em foo(bar)vez do foo=barque pode ser alcançado com um void foo(Bar bar)método mutador em uma _foovariável de membro.
Matthias

@Matthias Eu sei o que são propriedades, tenho escrito bastante C ++ e C # por mais de uma década, não estou discutindo sobre o benefício das propriedades e o que são, mas nunca precisei REALMENTE delas em C ++, na verdade você estamos dizendo que eles podem ser usados ​​como dados públicos e isso é verdade, mas há casos em C # em que você não pode nem usar propriedades diretamente, como passar uma propriedade por ref, enquanto com um campo público, você pode.
Eyal Solnik

4

Não .. Mas você deve considerar se é apenas a função get: set e nenhuma tarefa adicional pré-formada dentro dos métodos get: set, apenas torná-la pública.


2

Eu coletei as ideias de várias fontes C ++ e as coloquei em um exemplo legal, ainda bastante simples, para getters / setters em C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Resultado:

new canvas 256 256
resize to 128 256
resize to 128 64

Você pode testá-lo online aqui: http://codepad.org/zosxqjTX


Há uma sobrecarga de memória para manter autorreferências, + uma sintaxe de ctor intuitiva.
Red.Wave

Para propor propriedades? Eu acho que tem havido um monte de propostas rejeitadas.
Red.Wave

@Red.Wave Curve-se ao senhor e mestre da rejeição então. Bem-vindo ao C ++. O Clang e o MSVC têm extensões personalizadas para propriedades, se você não quiser as referências próprias.
lama12345

Eu nunca posso me curvar. Nem todos os recursos são adequados, eu acho. Para mim, os objetos são muito mais do que um par de funções setter + getter. Eu tentei as próprias implementações evitando a sobrecarga desnecessária de memória permanente, mas a sintaxe e a tarefa de declarar instâncias não eram satisfatórias; Fui atraído por macros declarativas, mas não sou um grande fã de macros de qualquer maneira. E minha abordagem, finalmente, levou a propriedades acessadas com sintaxe de função, que muitos incluindo eu não aprovamos.
Red.Wave

0

Sua classe realmente precisa impor alguma invariante ou é apenas um agrupamento lógico de elementos de membro? Se for o último, você deve considerar tornar a coisa uma estrutura e acessar os membros diretamente.


0

Há um conjunto de macros escritas aqui . ISTO tem declarações de propriedade convenientes para tipos de valor, tipos de referência, tipos somente leitura, tipos fortes e fracos.

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.