Ponteiro para o membro de dados da classe “:: *”


242

Me deparei com este estranho trecho de código que compila bem:

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;
    return 0;
}

Por que o C ++ possui esse ponteiro para um membro de dados não estáticos de uma classe? Qual é a utilidade desse ponteiro estranho no código real?


Aqui é onde eu o encontrei, me confundiu também ... mas faz sentido agora: stackoverflow.com/a/982941/211160
HostileFork diz que não confia em SE

Respostas:


189

É um "ponteiro para membro" - o código a seguir ilustra seu uso:

#include <iostream>
using namespace std;

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;

    Car c1;
    c1.speed = 1;       // direct access
    cout << "speed is " << c1.speed << endl;
    c1.*pSpeed = 2;     // access via pointer to member
    cout << "speed is " << c1.speed << endl;
    return 0;
}

Quanto ao motivo pelo qual você gostaria de fazer isso, ele fornece outro nível de indireção que pode resolver alguns problemas complicados. Mas, para ser sincero, nunca precisei usá-los em meu próprio código.

Edit: Não consigo pensar de maneira improvável em um uso convincente de ponteiros para dados de membros. O ponteiro para funções-membro pode ser usado em arquiteturas conectáveis, mas mais uma vez produzir um exemplo em um espaço pequeno me derrota. A seguir, é minha melhor tentativa (não testada) - uma função Apply que faria algum processamento antes e depois antes de aplicar uma função membro selecionada pelo usuário a um objeto:

void Apply( SomeClass * c, void (SomeClass::*func)() ) {
    // do hefty pre-call processing
    (c->*func)();  // call user specified function
    // do hefty post-call processing
}

Os parênteses ao redor c->*funcsão necessários porque o ->*operador tem precedência menor do que o operador de chamada de função.


3
Você poderia mostrar um exemplo de uma situação complicada em que isso é útil? Obrigado.
Ashwin Nanjappa 22/03/09

Eu tenho um exemplo de uso de ponteiro para membro em uma classe Traits em outra resposta SO .
Mike DeSimone

Um exemplo é escrever uma classe do tipo "retorno de chamada" para algum sistema baseado em eventos. O sistema de inscrição de eventos da UI da CEGUI, por exemplo, recebe um retorno de chamada modelo que armazena um ponteiro para uma função membro de sua escolha, para que você possa especificar um método para manipular o evento.
Benji XVI

2
Há um exemplo legal bonita de ponteiro-to- dados -member uso em uma função de modelo neste código
alveko

3
Recentemente, usei ponteiros para membros de dados na estrutura de serialização. O objeto estático do empacotador foi inicializado com uma lista de wrappers contendo ponteiro para membros de dados serializáveis. Um protótipo inicial desse código.
Alexey Biryukov

79

Este é o exemplo mais simples que consigo pensar que transmite os raros casos em que esse recurso é pertinente:

#include <iostream>

class bowl {
public:
    int apples;
    int oranges;
};

int count_fruit(bowl * begin, bowl * end, int bowl::*fruit)
{
    int count = 0;
    for (bowl * iterator = begin; iterator != end; ++ iterator)
        count += iterator->*fruit;
    return count;
}

int main()
{
    bowl bowls[2] = {
        { 1, 2 },
        { 3, 5 }
    };
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::apples) << " apples\n";
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges\n";
    return 0;
}

O ponto a ser observado aqui é o ponteiro passado para count_fruit. Isso evita que você escreva funções count_apples e count_oranges separadas.


3
Não deveria ser &bowls.applese &bowls.oranges? &bowl::applese &bowl::orangesnão aponta para nada.
Dan Nissenbaum 30/03

19
&bowl::applese &bowl::orangesnão aponte para membros de um objeto ; eles apontam para os membros de uma classe . Eles precisam ser combinados com um ponteiro para um objeto real antes de apontar para alguma coisa. Essa combinação é alcançada com o ->*operador.
John McFarlane

58

Outra aplicação são listas intrusivas. O tipo de elemento pode dizer à lista quais são seus próximos / anteriores ponteiros. Portanto, a lista não usa nomes codificados, mas ainda pode usar ponteiros existentes:

// say this is some existing structure. And we want to use
// a list. We can tell it that the next pointer
// is apple::next.
struct apple {
    int data;
    apple * next;
};

// simple example of a minimal intrusive list. Could specify the
// member pointer as template argument too, if we wanted:
// template<typename E, E *E::*next_ptr>
template<typename E>
struct List {
    List(E *E::*next_ptr):head(0), next_ptr(next_ptr) { }

    void add(E &e) {
        // access its next pointer by the member pointer
        e.*next_ptr = head;
        head = &e;
    }

    E * head;
    E *E::*next_ptr;
};

int main() {
    List<apple> lst(&apple::next);

    apple a;
    lst.add(a);
}

Se esta é realmente uma lista vinculada, você não gostaria de algo assim: void add (E * e) {e -> * next_ptr = head; cabeça = e; } ??
eeeeaaii

4
@eee Eu recomendo que você leia sobre os parâmetros de referência. O que eu fiz é basicamente equivalente ao que você fez.
Johannes Schaub - litb 25/08

+1 no seu exemplo de código, mas não vi necessidade de usar o ponteiro para membro, outro exemplo?
Alcott

3
@ Alcott: Você pode aplicá-lo a outras estruturas do tipo lista vinculada, nas quais o próximo ponteiro não é nomeado next.
Icktoofay

41

Aqui está um exemplo do mundo real no qual estou trabalhando agora, a partir de sistemas de processamento / controle de sinal:

Suponha que você tenha alguma estrutura que represente os dados que você está coletando:

struct Sample {
    time_t time;
    double value1;
    double value2;
    double value3;
};

Agora, suponha que você os coloque em um vetor:

std::vector<Sample> samples;
... fill the vector ...

Agora, suponha que você queira calcular alguma função (digamos a média) de uma das variáveis ​​em um intervalo de amostras e que você queira fatorar esse cálculo médio em uma função. O ponteiro para membro facilita:

double Mean(std::vector<Sample>::const_iterator begin, 
    std::vector<Sample>::const_iterator end,
    double Sample::* var)
{
    float mean = 0;
    int samples = 0;
    for(; begin != end; begin++) {
        const Sample& s = *begin;
        mean += s.*var;
        samples++;
    }
    mean /= samples;
    return mean;
}

...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);

Nota Editada em 05/08/2016 para uma abordagem mais concisa das funções de modelo

E, é claro, você pode modelá-lo para calcular uma média para qualquer iterador futuro e qualquer tipo de valor que suporte adição além de si e divisão por size_t:

template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
    using T = typename std::iterator_traits<Titer>::value_type;
    S sum = 0;
    size_t samples = 0;
    for( ; begin != end ; ++begin ) {
        const T& s = *begin;
        sum += s.*var;
        samples++;
    }
    return sum / samples;
}

struct Sample {
    double x;
}

std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);

EDIT - O código acima tem implicações de desempenho

Você deve observar, como logo descobri, que o código acima tem algumas implicações sérias de desempenho. O resumo é que, se você está calculando uma estatística resumida em uma série temporal, ou calculando uma FFT, etc., deve armazenar os valores para cada variável contiguamente na memória. Caso contrário, a iteração sobre a série causará uma falta de cache para cada valor recuperado.

Considere o desempenho deste código:

struct Sample {
  float w, x, y, z;
};

std::vector<Sample> series = ...;

float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
  sum += *it.x;
  samples++;
}
float mean = sum / samples;

Em muitas arquiteturas, uma instância de Samplepreencherá uma linha de cache. Portanto, em cada iteração do loop, uma amostra será extraída da memória para o cache. Serão usados ​​4 bytes da linha de cache e o restante jogado fora, e a próxima iteração resultará em outra falha de cache, acesso à memória e assim por diante.

Muito melhor para fazer isso:

struct Samples {
  std::vector<float> w, x, y, z;
};

Samples series = ...;

float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
  sum += *it;
  samples++;
}
float mean = sum / samples;

Agora, quando o primeiro valor x for carregado da memória, os próximos três também serão carregados no cache (supondo um alinhamento adequado), o que significa que você não precisa de nenhum valor carregado para as próximas três iterações.

O algoritmo acima pode ser melhorado um pouco mais através do uso de instruções SIMD em, por exemplo, arquiteturas SSE2. No entanto, elas funcionam muito melhor se os valores estiverem todos contíguos na memória e você pode usar uma única instrução para carregar quatro amostras juntas (mais nas versões posteriores do SSE).

YMMV - projete suas estruturas de dados para se adequar ao seu algoritmo.


Isto e excelente. Estou prestes a implementar algo muito semelhante e agora não preciso descobrir a sintaxe estranha! Obrigado!
Nicu Stiurca 26/03

Esta é a melhor resposta. A double Sample::*parte é a chave!
Eyal

37

Mais tarde, você pode acessar esse membro, em qualquer instância:

int main()
{    
  int Car::*pSpeed = &Car::speed;    
  Car myCar;
  Car yourCar;

  int mySpeed = myCar.*pSpeed;
  int yourSpeed = yourCar.*pSpeed;

  assert(mySpeed > yourSpeed); // ;-)

  return 0;
}

Observe que você precisa de uma instância para chamá-la, para que não funcione como um delegado.
É usado raramente, eu precisei talvez uma ou duas vezes em todos os meus anos.

Normalmente, usar uma interface (ou seja, uma classe base pura em C ++) é a melhor opção de design.


Mas certamente isso é apenas uma má prática? deve fazer algo como youcar.setspeed (mycar.getpspeed)
thecoshman

9
@ thecoshman: depende inteiramente - ocultar os membros dos dados por trás dos métodos set / get não é um encapsulamento e apenas uma tentativa das empregadas de leite de abstrair a interface. Em muitos cenários, a "desnormalização" para membros públicos é uma escolha razoável. Mas essa discussão provavelmente excede os limites da funcionalidade de comentários.
Peterchen 12/10/10

4
+1 por apontar, se bem entendi, que esse é um ponteiro para um membro de qualquer instância, e não um ponteiro para um valor específico de uma instância, que é a parte que estava completamente ausente.
precisa saber é o seguinte

@Fellowshee Você entende corretamente :) (enfatizou isso na resposta).
Peterchen 22/05

26

A IBM possui mais documentação sobre como usar isso. Resumidamente, você está usando o ponteiro como um deslocamento para a classe. Você não pode usar esses ponteiros além da classe a que se referem, portanto:

  int Car::*pSpeed = &Car::speed;
  Car mycar;
  mycar.*pSpeed = 65;

Parece um pouco obscuro, mas um aplicativo possível é se você estiver tentando escrever código para desserializar dados genéricos em muitos tipos diferentes de objetos, e seu código precisar lidar com tipos de objetos sobre os quais não sabe absolutamente nada (por exemplo, seu código é em uma biblioteca e os objetos nos quais você desserializa foram criados por um usuário da sua biblioteca). Os ponteiros de membro oferecem uma maneira genérica e semi-legível de se referir às compensações individuais dos membros de dados, sem a necessidade de recorrer a truques de tipo * nulos * da maneira que você pode usar para estruturas C.


Você poderia compartilhar um exemplo de trecho de código em que essa construção é útil? Obrigado.
Ashwin Nanjappa 22/03/09

2
No momento, estou fazendo muito disso devido a algum trabalho do DCOM e ao uso de classes de recursos gerenciados, que envolvem um pouco de trabalho antes de cada chamada, e o uso de membros de dados para representação interna para enviar para com, além de modelagem, faz muito código de placa de caldeira muito menor
Dan

19

Permite ligar variáveis ​​e funções de membros de maneira uniforme. A seguir, é apresentado um exemplo com a sua classe Car. O uso mais comum seria obrigatório std::pair::firste ::secondao usar nos algoritmos STL e no Boost em um mapa.

#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>


class Car {
public:
    Car(int s): speed(s) {}
    void drive() {
        std::cout << "Driving at " << speed << " km/h" << std::endl;
    }
    int speed;
};

int main() {

    using namespace std;
    using namespace boost::lambda;

    list<Car> l;
    l.push_back(Car(10));
    l.push_back(Car(140));
    l.push_back(Car(130));
    l.push_back(Car(60));

    // Speeding cars
    list<Car> s;

    // Binding a value to a member variable.
    // Find all cars with speed over 60 km/h.
    remove_copy_if(l.begin(), l.end(),
                   back_inserter(s),
                   bind(&Car::speed, _1) <= 60);

    // Binding a value to a member function.
    // Call a function on each car.
    for_each(s.begin(), s.end(), bind(&Car::drive, _1));

    return 0;
}

11

Você pode usar uma matriz de ponteiro para dados de membros (homogêneos) para ativar uma interface dupla de membro nomeado (iexdata) e matriz de subscrito (ie x [idx]).

#include <cassert>
#include <cstddef>

struct vector3 {
    float x;
    float y;
    float z;

    float& operator[](std::size_t idx) {
        static float vector3::*component[3] = {
            &vector3::x, &vector3::y, &vector3::z
        };
        return this->*component[idx];
    }
};

int main()
{
    vector3 v = { 0.0f, 1.0f, 2.0f };

    assert(&v[0] == &v.x);
    assert(&v[1] == &v.y);
    assert(&v[2] == &v.z);

    for (std::size_t i = 0; i < 3; ++i) {
        v[i] += 1.0f;
    }

    assert(v.x == 1.0f);
    assert(v.y == 2.0f);
    assert(v.z == 3.0f);

    return 0;
}

Eu já vi isso implementado com mais freqüência usando uma união anônima, incluindo um campo de matriz v [3], uma vez que evita uma indireção, mas inteligente, no entanto, e potencialmente útil para campos não contíguos.
Dwayne Robinson

2
@DwayneRobinson, mas usar um uniontipo de trocadilho dessa maneira não é permitido pelo padrão, pois invoca inúmeras formas de comportamento indefinido ... enquanto essa resposta está correta.
underscore_d

Esse é um exemplo interessante, mas o operador [] pode ser reescrito sem ponteiro para componente: Ou float *component[] = { &x, &y, &z }; return *component[idx];seja, o ponteiro para componente parece não ter outro objetivo, exceto a ofuscação.
tobi_s

2

Uma maneira que usei é se tenho duas implementações de como fazer algo em uma classe e quero escolher uma em tempo de execução sem ter que passar continuamente por uma instrução if

class Algorithm
{
public:
    Algorithm() : m_impFn( &Algorithm::implementationA ) {}
    void frequentlyCalled()
    {
        // Avoid if ( using A ) else if ( using B ) type of thing
        (this->*m_impFn)();
    }
private:
    void implementationA() { /*...*/ }
    void implementationB() { /*...*/ }

    typedef void ( Algorithm::*IMP_FN ) ();
    IMP_FN m_impFn;
};

Obviamente, isso só é praticamente útil se você sentir que o código está sendo martelado o suficiente para que a instrução if esteja atrasando as coisas feitas, por exemplo. profundamente nas entranhas de algum algoritmo intensivo em algum lugar. Eu ainda acho que é mais elegante do que a declaração if, mesmo em situações em que não tem uso prático, mas essa é apenas a minha opinião.


Basicamente, você pode conseguir o mesmo com o resumo Algorithme duas classes derivadas, por exemplo, AlgorithmAe AlgorithmB. Nesse caso, ambos os algoritmos são bem separados e garantem o teste independente.
shycha

2

Ponteiros para classes não são ponteiros reais ; uma classe é uma construção lógica e não possui existência física na memória; no entanto, quando você constrói um ponteiro para um membro de uma classe, ele desloca um objeto da classe do membro onde o membro pode ser encontrado; Isso dá uma conclusão importante: Como os membros estáticos não estão associados a nenhum objeto, um ponteiro para um membro NÃO PODE apontar para um membro estático (dados ou funções) de qualquer maneira Considere o seguinte:

class x {
public:
    int val;
    x(int i) { val = i;}

    int get_val() { return val; }
    int d_val(int i) {return i+i; }
};

int main() {
    int (x::* data) = &x::val;               //pointer to data member
    int (x::* func)(int) = &x::d_val;        //pointer to function member

    x ob1(1), ob2(2);

    cout <<ob1.*data;
    cout <<ob2.*data;

    cout <<(ob1.*func)(ob1.*data);
    cout <<(ob2.*func)(ob2.*data);


    return 0;
}

Fonte: A referência completa C ++ - Herbert Schildt 4th Edition


0

Eu acho que você só gostaria de fazer isso se os dados do membro fossem muito grandes (por exemplo, um objeto de outra classe bastante pesada) e você tiver alguma rotina externa que funcione apenas em referências a objetos dessa classe. Você não deseja copiar o objeto membro, portanto, isso permite distribuí-lo.


0

Aqui está um exemplo em que o ponteiro para membros de dados pode ser útil:

#include <iostream>
#include <list>
#include <string>

template <typename Container, typename T, typename DataPtr>
typename Container::value_type searchByDataMember (const Container& container, const T& t, DataPtr ptr) {
    for (const typename Container::value_type& x : container) {
        if (x->*ptr == t)
            return x;
    }
    return typename Container::value_type{};
}

struct Object {
    int ID, value;
    std::string name;
    Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};

std::list<Object*> objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"), new Object(9,12,"Rob"),
    new Object(2,11,"Tom"), new Object(15,16,"John") };

int main() {
    const Object* object = searchByDataMember (objects, 11, &Object::value);
    std::cout << object->name << '\n';  // Tom
}

0

Suponha que você tenha uma estrutura. Dentro dessa estrutura há * algum tipo de nome * duas variáveis ​​do mesmo tipo, mas com significado diferente

struct foo {
    std::string a;
    std::string b;
};

Ok, agora digamos que você tenha vários foos em um contêiner:

// key: some sort of name, value: a foo instance
std::map<std::string, foo> container;

Ok, agora suponha que você carregue os dados de fontes separadas, mas os dados são apresentados da mesma maneira (por exemplo, você precisa do mesmo método de análise).

Você poderia fazer algo assim:

void readDataFromText(std::istream & input, std::map<std::string, foo> & container, std::string foo::*storage) {
    std::string line, name, value;

    // while lines are successfully retrieved
    while (std::getline(input, line)) {
        std::stringstream linestr(line);
        if ( line.empty() ) {
            continue;
        }

        // retrieve name and value
        linestr >> name >> value;

        // store value into correct storage, whichever one is correct
        container[name].*storage = value;
    }
}

std::map<std::string, foo> readValues() {
    std::map<std::string, foo> foos;

    std::ifstream a("input-a");
    readDataFromText(a, foos, &foo::a);
    std::ifstream b("input-b");
    readDataFromText(b, foos, &foo::b);
    return foos;
}

Nesse ponto, a chamada readValues()retornará um contêiner com um uníssono de "entrada-a" e "entrada-b"; todas as teclas estarão presentes e os foos com a ou b ou ambos.


0

Apenas para adicionar alguns casos de uso à resposta do @ anon e do @ Oktalist, aqui está um ótimo material de leitura sobre a função ponteiro para membro e dados de ponteiro para membro.

https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf


o link está morto. É por isso que respostas somente de links não são esperadas aqui. Pelo menos resumir o conteúdo do link, caso contrário, sua resposta se torna inválida quando a ligação apodrece
phuclv
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.