Sınıf veri elemanı işaretçisi “:: *”


243

İyi derleyen bu garip kod snippet'i ile karşılaştım:

class Car
{
    public:
    int speed;
};

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

Neden C ++ bu işaretçiyi bir sınıfın statik olmayan bir veri üyesine sahip? Ne gerçek kodda bu tuhaf pointer kullanılmasıdır?


İşte bulduğum yer, beni de karıştırdı ... ama şimdi mantıklı: stackoverflow.com/a/982941/211160
HostileFork SE

Yanıtlar:


190

Bu bir "üyeye işaretçi" dir - aşağıdaki kod kullanımını gösterir:

#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;
}

Gelince neden bunu yapmak isteyeyim, iyi size bazı zor sorunları çözebilir dolaylama başka seviyesini verir. Ama dürüst olmak gerekirse, bunları asla kendi kodumda kullanmak zorunda kalmadım.

Düzenleme: Üye verileri işaretçiler için inandırıcı bir kullanım off-el düşünemiyorum. İşaretçi-üye işlevleri takılabilir mimarilerde kullanılabilir, ancak bir kez daha küçük bir alanda bir örnek üretmek beni yener. Aşağıdakiler benim en iyi (denenmemiş) denememdir - bir nesneye kullanıcı tarafından seçilen üye işlevini uygulamadan önce bazı ön ve son işlemleri yapacak bir Uygula işlevi:

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

Etraftaki parantezler c->*funcgereklidir, çünkü ->*işleç, işlev çağrısı işleçinden daha düşük önceliğe sahiptir.


3
Bunun yararlı olduğu zor bir durum örneği gösterebilir misiniz? Teşekkürler.
Ashwin Nanjappa

Başka bir SO yanıt bir Traits sınıfında işaretçi-üye kullanma örneği var .
Mike DeSimone

Buna örnek olarak, olay tabanlı bazı sistemler için "geri çağrı" türünde bir sınıf yazmaktır. Örneğin, CEGUI'nin UI olay abonelik sistemi, olayı işlemek için bir yöntem belirleyebilmeniz için, işaretçiyi seçtiğiniz bir üye işlevine depolayan şablonlu bir geri çağrı alır.
Benji XVI

2
Bu kodda bir şablon işlevinde işaretçi- veri - üye kullanımına oldukça havalı bir örnek var
alveko

3
Son zamanlarda serileştirme çerçevesinde veri üyelerine işaretçiler kullandım. Statik marshaller nesnesi, serileştirilebilir veri üyelerine işaretçi içeren paketlerin listesi ile başlatıldı. Bu kodun erken bir prototipi.
Alexey Biryukov

79

Bu, bu özelliğin ilgili olduğu nadir durumları ilettiğini düşünebileceğim en basit örnektir:

#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;
}

Burada dikkat edilmesi gereken şey count_fruit öğesine iletilen işaretçidir. Bu, ayrı count_apples ve count_oranges işlevleri yazmak zorunda kalmanızı sağlar.


3
Olmamalı &bowls.applesve &bowls.oranges? &bowl::applesve &bowl::orangeshiçbir şeye işaret etmiyor.
Dan Nissenbaum

19
&bowl::applesve &bowl::orangesbir nesnenin üyelerine işaret etmeyin ; bir sınıfın üyelerine işaret ediyorlar . Bir şeye işaret etmeden önce gerçek bir nesneye bir işaretçi ile birleştirilmeleri gerekir. Bu kombinasyon ->*operatör ile elde edilir .
John McFarlane

58

Başka bir uygulama müdahaleci listeleridir. Öğe türü listeye sonraki / önceki işaretlerinin ne olduğunu söyleyebilir. Bu nedenle liste sabit kodlu adları kullanmaz, ancak mevcut işaretçileri kullanmaya devam edebilir:

// 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);
}

Bu gerçekten bağlantılı bir listeyse böyle bir şey istemezsiniz: void add (E * e) {e -> * next_ptr = head; kafa = e; } ??
eeeeaaii

4
@eee Referans parametreleri hakkında okumanızı tavsiye ederim. Yaptığım temelde yaptığınızla eşdeğer.
Johannes Schaub - litb

Kod örneğiniz için +1, ancak işaretçiden üyeye kullanım için herhangi bir gereklilik görmedim, başka bir örnek?
Alcott

3
@Alcott: Bir sonraki işaretçinin adlandırılmadığı diğer bağlantılı liste benzeri yapılara uygulayabilirsiniz next.
icktoofay

41

İşte sinyal işleme / kontrol sistemlerinden şu anda üzerinde çalıştığım gerçek dünya örneği:

Topladığınız verileri temsil eden bir yapıya sahip olduğunuzu varsayalım:

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

Şimdi bunları bir vektöre doldurduğunuzu varsayalım:

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

Şimdi, bir dizi numune üzerinde değişkenlerden birinin bazı fonksiyonlarını (ortalamasını söyleyin) hesaplamak istediğinizi ve bu ortalama hesaplamayı bir fonksiyonda hesaba katmak istediğinizi varsayalım. İşaretçi-üye bunu kolaylaştırır:

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);

Not Daha kısa şablon işlevi yaklaşımı için 2016/08/05 tarihinde düzenlendi

Ve elbette, herhangi bir ileri yineleyici ve kendisiyle toplamayı ve size_t'ye bölünmeyi destekleyen herhangi bir değer türü için bir ortalama hesaplamak için bunu şablonlayabilirsiniz:

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 - Yukarıdaki kodun performans sonuçları vardır

Kısa süre önce keşfettiğim gibi, yukarıdaki kodun bazı ciddi performans etkileri olduğunu not etmelisiniz. Özet, bir zaman serisinde özet bir istatistik hesaplarsanız veya bir FFT vb. Hesaplıyorsanız, her değişkenin değerlerini sürekli olarak bellekte saklamanızdır. Aksi takdirde, seri üzerinden yineleme yapılması alınan her değer için önbellek kaybına neden olur.

Bu kodun performansını göz önünde bulundurun:

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;

Birçok mimaride, bir örneği Sampleönbellek satırını dolduracaktır. Bu nedenle, döngünün her yinelemesinde, bir örnek bellekten önbelleğe alınır. Önbellek satırından 4 bayt kullanılacak ve geri kalanı atılacak ve bir sonraki yineleme başka bir önbellek kaybına, bellek erişimine vb.

Bunu yapmak için çok daha iyi:

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;

Şimdi ilk x değeri bellekten yüklendiğinde, sonraki üç değer de önbelleğe yüklenir (uygun hizalama varsayalım), yani sonraki üç yineleme için yüklenen herhangi bir değere ihtiyacınız yoktur.

Yukarıdaki algoritma, örneğin SSE2 mimarileri üzerinde SIMD talimatları kullanılarak biraz daha geliştirilebilir. Ancak, değerlerin tümü bellekte bitişikse bunlar çok daha iyi çalışır ve dört örneği birlikte yüklemek için tek bir talimat kullanabilirsiniz (daha sonraki SSE sürümlerinde daha fazla).

YMMV - veri yapılarınızı algoritmanıza uyacak şekilde tasarlayın.


Bu mükemmel. Çok benzer bir şey uygulamak üzereyim ve şimdi garip sözdizimini bulmak zorunda değilim! Teşekkürler!
Nicu Stiurca

Bu en iyi cevap. double Sample::*Bölüm anahtarıdır!
Eyal

37

Daha sonra bu üyeye her durumda erişebilirsiniz :

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

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

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

  return 0;
}

Bunu çağırmak için bir örneğe ihtiyacınız olduğunu unutmayın, böylece temsilci gibi çalışmaz.
Nadiren kullanılır, tüm yıllar boyunca belki bir veya iki kez ihtiyacım vardı.

Normalde bir arayüz kullanmak (yani C ++ 'da saf bir temel sınıf) daha iyi bir tasarım seçimidir.


Ama elbette bu sadece kötü bir uygulama mı? youcar.setspeed (mycar.getpspeed) gibi bir şey yapmalı
thecoshman

9
@thecoshman: tamamen bağlıdır - veri üyelerini set / get yöntemlerinin arkasına saklamak kapsülleme değildir ve sadece arayüzel soyutlamada bir sütçü girişimidir. Birçok senaryoda, kamu üyelerine "denormalizasyon" makul bir seçimdir. Ancak bu tartışma muhtemelen yorum işlevselliğinin sınırlarını aşıyor.
peterchen

4
Doğru anladıysam, bunun herhangi bir örneğin bir üyesine bir işaretçi olduğunu ve tamamen eksik olduğum bölüm olan bir örneğin belirli bir değerine bir işaretçi olmadığını belirtmek için +1.
johnbakers

@Fellowshee Doğru anlıyorsunuz :) (cevapta vurguladı).
peterchen

26

IBM'in bunun nasıl kullanılacağına ilişkin başka belgeleri de vardır. Kısaca, işaretçiyi sınıfa ofset olarak kullanıyorsunuz. Bu işaretçileri atıfta bulundukları sınıfın dışında kullanamazsınız;

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

Biraz belirsiz görünüyor, ancak olası bir uygulama, genel verilerin çoğunu farklı nesne türlerine serileştirmek için kod yazmaya çalışıyorsanız ve kodunuzun kesinlikle hakkında hiçbir şey bilmediği nesne türlerini işlemesi gerekiyorsa (örneğin, kodunuz bir kitaplıkta ve serisini kaldırdığınız nesneler kitaplığınızın bir kullanıcısı tarafından oluşturuldu). Üye işaretçileri, C yapıları için olabildiğince tipik boşluklara başvurmanıza gerek kalmadan, tek tek veri üye ofsetlerine atıfta bulunmanın genel, yarı okunaklı bir yolunu sunar.


Bu yapının yararlı olduğu bir kod snippet örneğini paylaşabilir misiniz? Teşekkürler.
Ashwin Nanjappa

2
Şu anda bunu bir sürü DCOM çalışması yapmak ve her çağrı önce biraz iş yapmak içeren yönetilen kaynak sınıfları kullanarak ve com, artı templating, göndermek için dahili temsil için veri üyeleri kullanarak bir sürü yapıyorum kazan plaka kodu çok daha küçük
Dan

19

Üye değişkenleri ve işlevleri tek biçimli bir şekilde bağlanmayı mümkün kılar. Araç sınıfınızla ilgili örnek aşağıdadır. Daha yaygın kullanım bağlayıcıdır std::pair::firstve ::secondbir harita üzerinde STL algoritmaları ve Boost kullanılırken.

#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

İkili, adlandırılmış üye (iexdata) ve dizi alt simge (yani x [idx]) arabirimini etkinleştirmek için üye verilerini (homojen) bir işaretçi dizisi kullanabilirsiniz.

#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;
}

Ben daha sık bu bir dolaylı, ancak yine de zeki ve potansiyel bitişik olmayan alanlar için yararlı bir dizi alan v [3] dahil olmak üzere anonim bir birlik kullanılarak uygulandığını gördüm.
Dwayne Robinson

2
@DwayneRobinson, ancak bu unionşekilde bir tip- pun'a kullanmak için standart olarak izin verilmez çünkü tanımlanmamış davranışların birçok biçimini çağırır ... oysa bu cevap tamamdır.
underscore_d

Bu düzgün bir örnektir, ancak [] operatörü işaretçi-bileşen olmadan yeniden yazılabilir: float *component[] = { &x, &y, &z }; return *component[idx];Yani, işaretçi-bileşen gizleme dışında bir amaca hizmet etmiyor gibi görünmektedir.
tobi_s

2

Bunu kullanmanın bir yolu, bir sınıfta bir şeylerin nasıl yapılacağı konusunda iki uygulamam varsa ve sürekli olarak bir if ifadesinden geçmek zorunda kalmadan çalışma zamanında bir tane seçmek istiyorum.

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;
};

Açıkçası bu sadece eğer kod if if gibi şeyler yavaşlatıyor yeterince dövülmüş hissediyorsanız pratik olarak yararlıdır örneğin. bir yerlerde bazı yoğun algoritmaların derinliklerinde. Hala pratik kullanımı olmayan durumlarda bile if ifadesinden daha zarif olduğunu düşünüyorum ama bu sadece benim görüşüm.


Temel olarak, aynısını soyut Algorithmve iki türetilmiş sınıfla, örneğin AlgorithmAve AlgorithmB. Böyle bir durumda, her iki algoritma da iyi ayrılır ve bağımsız olarak test edilmeleri sağlanır.
shycha

2

Sınıflara işaretçiler gerçek işaretçiler değildir ; sınıf mantıklı bir yapıdır ve bellekte fiziksel varlığı yoktur, ancak bir sınıfın bir üyesine bir işaretçi oluşturduğunuzda, üyenin bulunabileceği üye sınıfının bir nesnesine bir uzaklık verir; Bu önemli bir sonuç verir: Statik üyeler herhangi bir nesneyle ilişkilendirilmediğinden, bir üyeye işaretçi statik bir üyeye (veri veya işlevlere) işaret ETMEZ Aşağıdakileri göz önünde bulundurun:

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;
}

Kaynak: Komple Referans C ++ - Herbert Schildt 4. Baskı


0

Bunu sadece üye verileri oldukça büyükse (örneğin, başka bir oldukça ağır sınıfın nesnesi) ve yalnızca o sınıftaki nesnelere yapılan başvurularda çalışan bazı harici rutininiz varsa bunu yapmak isteyeceğinizi düşünüyorum. Üye nesneyi kopyalamak istemezsiniz, böylece bu nesneyi geçirmenize olanak tanır.


0

Veri üyelerine yönelik işaretçinin yararlı olabileceği bir örnek:

#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

Bir yapınız olduğunu varsayalım. Bu yapının içinde * bir tür isim * aynı tipte fakat farklı anlamı olan iki değişken vardır

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

Tamam, şimdi foobir kapta bir sürü s olduğunu varsayalım:

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

Tamam, şimdi verileri ayrı kaynaklardan yüklediğinizi, ancak verilerin aynı şekilde sunulduğunu varsayalım (örneğin, aynı ayrıştırma yöntemine ihtiyacınız var).

Bunun gibi bir şey yapabilirsiniz:

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;
}

Bu noktada, çağrı readValues()"giriş-a" ve "giriş-b" birlikteliğine sahip bir kap döndürür; tüm anahtarlar bulunacak ve foo'lar a veya b veya her ikisine birden sahip olacak.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.