Могу ли я назначить указатель данных элемента производному типу?
Это, вероятно, лучше всего показывает пример кода. Следующая команда не скомпилируется с 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
для поиска правильных данных элемента.
Ответы
Ответ 1
Это не ошибка компилятора, вы не можете этого сделать. (Но вы можете назначить Base:: * на Derived:: *).
Я не вижу веских оснований для ограничения (кроме того, чтобы обрабатывать случай множественного наследования, что еще более усложняло бы представление указателя-члена).
Ответ 2
Указатели на элементы в С++ не являются наглядными указателями, но скорее похожими на смещения заданного члена и специфичны для этого типа, поэтому то, что вы пытаетесь сделать, на самом деле не поддерживается.
Здесь достойная дискуссия на Stackoverflow С++: указатель на элемент данных класса.
Ответ 3
Есть много довольно сложных, некоторые не очень хорошо объяснены, и несколько плоских неправильных ответов в этой теме.
Но проблема, мне кажется, в том, что в Container
нет члена Base
- есть член Derived
. Вы не можете этого сделать:
Base Container::*ptr = &Container::data_;
... по той же причине вы не можете этого сделать:
int a;
long* pl = &a;
Во втором примере объект не является long
, это a int
. Аналогично, в первом примере объект не является Base
, это a Derived
.
Как возможная тангенциальная точка, мне кажется, что вы действительно хотите сделать, чтобы Base
был абстрактным классом и имел Container
элемент Base*
, а не член Derived
.
Ответ 4
Вам просто нужно написать:
Base* ptr = &container.data_;
но container
должен быть экземпляром container
, поэтому вам нужно создать где-то одну переменную этого типа.
Ответ 5
Вы не можете преобразовать 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();
}
Ответ 6
Вам нужно static_cast
выполнить это преобразование, как показано в 5.3.9/9. Эта причина заключается в том, что он действует как static_cast
от указателя родительского объекта к указателю на дочерние объекты. Другими словами, включение указателя на производный член в элемент-указатель-родитель позволит вам получить доступ к несуществующему производному члену от родительского объекта или указателя. Если бы стандарт разрешил это автоматически, было бы легко испортиться и попытаться получить доступ к дочернему элементу класса, который не соответствует соответствующему типу дочернего элемента (который содержит указанный член).
Без дополнительной информации это звучит так, как будто вам нужен другой/лучший интерфейс конструктора/набора в вашем классе Base
вместо того, чтобы пытаться использовать здесь указатели.
Ответ 7
Я думаю, что вам нужен "контейнер", т.е. структура, которая имеет только указатели:
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*>
Ответ 8
Что касается исходной проблемы, вы можете сделать это, указав на функции, вместо того, чтобы вводить базовые классы.
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);
}
Ответ 9
struct Container {
Derived data_;
};
int main(void)
{
Base Container::*ptr = &Container::data_;
}
Первая проблема заключается в том, что Container
не имеет члена, называемого ptr
Container container_object;
Base *ptr = container_object.data_;
Будет работать. Обратите внимание, что для создания элемента data_ должен быть контейнерный объект, и его необходимо будет опубликовать.
Альтернативой было бы, чтобы производный:: data_ был статическим членом.