Могу ли я присвоить указатель на данные члена производному типу? - PullRequest
13 голосов
/ 16 мая 2011

Это, вероятно, лучше всего показано с примером кода. Следующее не удается скомпилировать с g ++:

struct Base {
};

struct Derived : public Base {
};

struct Container {
    Derived data_;
};

int main(void) {
    Base Container::*ptr = &Container::data_;
}

Я получаю следующую ошибку: invalid conversion from 'Derived Container::*' to Base Container::*'. Разве это не разрешено языком? Это ошибка компилятора? Я использую неправильный синтаксис?

Пожалуйста, помогите!

Некоторые предыстории того, почему я пытаюсь сделать это: у меня есть несколько элементов данных-членов, которые я хочу использовать в основном в качестве их производных типов, но я хочу иметь возможность заполнить их некоторым неким общим кодом. Данные будут поступать в произвольном порядке и иметь строковую метку, которую я буду использовать для выбора соответствующих данных члена для заполнения. Я планировал создать std::map<std::string, Base Container::*> для назначения данных каждому члену через общий интерфейс. Я бы хотел избежать использования гигантской конструкции if else для поиска правильных данных о членах.

Ответы [ 9 ]

3 голосов
/ 16 мая 2011

Это не ошибка компилятора, вы не можете этого сделать. (Но вы можете назначить Base :: * производному :: *).

Я не вижу веских оснований для ограничения (за исключением того, что в случае множественного наследования это еще больше усложнит представление указателя на член).

1 голос
/ 16 мая 2011

В этой теме есть много довольно сложных, некоторые не совсем хорошо объясненных и несколько категорических неправильных ответов.

Но проблема, как мне кажется, в том, что просто нетBase член в Container - есть Derived член.Вы не можете сделать это:

Base Container::*ptr = &Container::data_;

... по той же причине, по которой вы не можете сделать это:

int a;
long* pl = &a;

Во втором примере объект не являетсяlong, это int.Точно так же, в первом примере объект не является Base, это Derived.

В качестве возможной точки касания, мне кажется, что то, что вы действительно хотите сделать, это иметь Baseбыть абстрактным классом и иметь Container иметь Base*, а не Derived член.

1 голос
/ 16 мая 2011

Указатели на элементы в C ++ не являются на самом деле указателями, но больше похожи на смещения на данный элемент и относятся к типу, поэтому то, что вы пытаетесь сделать, на самом деле не поддерживается.

Вот достойное обсуждение здесь Stackoverflow C ++: указатель на элемент данных класса .

0 голосов
/ 16 мая 2011

Что касается исходной проблемы, вы можете сделать это, используя указатель на функции вместо введения базовых классов.

class Container {
public:
  void set(std::string const& label, std::string const& value);

  void setName(std::string const& value) { _name = value; }
  void setAge(std::string const& age) {
    _age = boost::lexical_cast<size_t>(age);
  }

private:
  std::string _name;
  size_t _age;
};

Как реализовать set тогда?

// container.cpp
typedef void (Container::*SetterType)(std::string const&);
typedef std::map<std::string, SetterType> SettersMapType;

SettersMapType SettersMap =
  boost::assign::map_list_of("name", &Container::setName)
                            ("age", &Container::setAge);

void Container::set(std::string const& label, std::string const& value) {
  SettersMapType::const_iterator it = SettersMap.find(label);
  if (it == SettersMap.end()) { throw UnknownLabel(label); }

  SetterType setter = it->second;
  (this->*setter)(value);
}
0 голосов
/ 16 мая 2011
struct Container {
   Derived data_; 
};  

int main(void) 
{
   Base Container::*ptr = &Container::data_;
} 

Первая проблема заключается в том, что Container не имеет члена с именем ptr

Container container_object;
Base *ptr = container_object.data_;

Будет работать. Обратите внимание, что для создания элемента data_ должен существовать контейнерный объект, и он должен быть обнародован.

В качестве альтернативы будет производный :: data_ быть статическим членом.

0 голосов
/ 16 мая 2011

Я думаю, что вам нужен «контейнер», т. Е. Структура, в которой есть только указатели:

struct Container{
    Base* derivedAdata_;
    Base* derivedBdata_;
    ...
};

Теперь каждый из известных вам членов относится к определенному типу (например, DerivedA, DerivedB и т. Д.).), чтобы потом можно было их понижать.

Но сначала вы получаете данные (в произвольном порядке), но со строковым именем, поэтому у вас должна быть карта:

std::map<std::string, Base* Container::*>

И, должно быть, вы уже заполнили карту:

myMap["DerivedA"] = &Container::derivedAdata;
...

Теперь данные поступают, и вы начинаете заполнять контейнер:

instance.*(myMap[key]) = factory(key, data);

myMap[key] выбирает правый элемент контейнера и factory(key,data) создает экземпляры.

кстати, вы можете в любом случае иметь карту в качестве контейнера: std::map<std::string, Base*>

0 голосов
/ 16 мая 2011

Вы должны были бы static_cast сделать это преобразование, как показано в 5.3.9 / 9.Это объясняется тем, что он действует как static_cast от указателя родительского объекта к указателю дочернего объекта.Другими словами, помещение указателя на производный член в указатель на родительский элемент позволит вам получить доступ к несуществующему производному члену из родительского объекта или указателя.Если бы стандарт позволял это делать автоматически, было бы легко испортить и попытаться получить доступ к дочернему элементу в классе, который не имеет соответствующего дочернего типа (который содержит указанный элемент).как будто вам нужен другой / лучший интерфейс конструктора / набора в вашем классе Base вместо того, чтобы пытаться использовать указатели на член здесь.

0 голосов
/ 16 мая 2011

Вы не можете конвертировать C :: * A в C :: * B, даже если есть возможность преобразования между A и B.

Однако вы можете сделать this :

struct Base
{
    virtual ~Base() {}
    virtual void foo() { std::cout << "Base::foo()\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived::foo()\n"; }
};

struct Bar
{
    Base* x;

    Bar() : x(new Derived) {}
};

int main()
{
    Bar b;
    Base* Bar::*p = &Bar::x;
    (b.*p)->foo();
}
0 голосов
/ 16 мая 2011

Вам просто нужно написать:

Base* ptr = &container.data_;

, но container должен быть экземпляром Container, поэтому вам нужно где-то создать одну переменную этого типа.

...