Почему у С++ нет виртуальных переменных?
Это может быть задано в миллион раз раньше или может быть невероятно глупым, но почему это не реализовано?
class A
{
public:
A(){ a = 5;}
int a;
};
class B:public A
{
public:
B(){ a = 0.5;}
float a;
};
int main()
{
A * a = new B();
cout<<a->a;
getch();
return 0;
}
Этот код будет иметь доступ к A:: a. Как получить доступ к B:: a?
Ответы
Ответ 1
Для доступа к B:: a:
cout << static_cast<B*>(a)->a;
Чтобы явно получить доступ к A:: a и B:: a:
cout << static_cast<B*>(a)->A::a;
cout << static_cast<B*>(a)->B::a;
(dynamic_cast иногда лучше, чем static_cast, но он не может использоваться здесь, потому что A и B не являются полиморфными.)
Что касается того, почему С++ не имеет виртуальных переменных: виртуальные функции допускают полиморфизм; другими словами, они позволяют рассматривать классы двух разных типов одинаково, вызывая код, при этом любые различия во внутреннем поведении этих двух классов инкапсулируются внутри виртуальных функций.
Виртуальные переменные-члены не имеют смысла; нет никакого поведения для инкапсуляции при простом доступе к переменной.
Также имейте в виду, что С++ является статически типизированным. Виртуальные функции позволяют изменять поведение во время выполнения; ваш примерный код пытается изменить не только поведение, но типы данных во время выполнения (A::a
is int
, B::a
is float
), а С++ не работает таким образом. Если вам нужно разместить разные типы данных во время выполнения, вам необходимо инкапсулировать эти различия в виртуальных функциях, которые скрывают различия в типах данных. Например (только для демонстрационного кода, для реального кода, вы оператор перегрузки < < вместо):
class A
{
public:
A(){ a = 5;}
int a;
virtual void output_to(ostream& o) const { o << a; }
};
class B:public A
{
public:
B(){ a = 0.5;}
float a;
void output_to(ostream& o) const { o << a; }
};
Также имейте в виду, что включение переменных-членов таким образом может нарушить инкапсуляцию и, как правило, неодобрительно.
Ответ 2
Не публиковать данные и получать доступ к ним через виртуальные функции.
Посмотрите на мгновение, как то, что вы просите, должно быть реализовано. В принципе, это заставит любой доступ к любому члену данных пройти виртуальную функцию. Помните, что вы получаете доступ к данным через указатель на объект A, а класс A не знает, что вы делали в классе B.
Другими словами, мы могли бы сделать доступ к любому элементу данных куда угодно медленнее - или вы могли бы написать виртуальный метод. Угадайте, какие дизайнеры С++ выбрали.
Ответ 3
Вы не можете этого сделать, а С++ не поддерживает его, потому что он ломается с фундаментальными принципами С++.
A float
- это другой тип, чем int
, и поиск имени, а также определение того, какие преобразования потребуются для назначения значений, происходит во время компиляции. Однако то, что действительно названо a->a
, включая его фактический тип, будет известно только во время выполнения.
Вы можете использовать шаблоны для параметризации класса A
template<typename T>
class A
{
public:
// see also constructor initializer lists
A(T t){ a = t; }
T a;
};
Затем вы можете передать тип, однако только во время компиляции по вышеупомянутой основной причине.
A<int> a(5);
A<float> b(5.5f);
Ответ 4
(dynamic_cast<B*>(a))->a
?
Зачем вам это нужно? Не достаточно ли виртуальных функций?
Ответ 5
Оставляя в стороне аргумент, что виртуальные методы должны быть частными, виртуальные методы предназначены как дополнительный уровень инкапсуляции (инкапсулирующие вариации в поведении), Прямой доступ к полям идет против инкапсуляции, чтобы начать с этого, было бы немного лицемерно создавать виртуальные поля. И поскольку поля не определяют поведение, они просто хранят данные, на самом деле нет никакого поведения для виртуализации. Сам факт, что у вас есть открытый int или float, является анти-шаблоном.
Ответ 6
Вы можете отключить переменную для доступа к B::a
.
Что-то вроде:
((B*)a)->a
Я думаю, что это то же самое в большинстве языков программирования OO. Я не могу представить ни одного, реализующего концепцию virtual variables
...
Ответ 7
Вы можете создать такой эффект следующим образом:
#include <iostream>
class A {
public:
double value;
A() {}
virtual ~A() {}
virtual void doSomething() {}
};
class B : public A {
public:
void doSomething() {
A::value = 3.14;
}
};
int main() {
A* a = new B();
a->doSomething();
std::cout << a->value << std::endl;
delete a;
return 0;
}
В приведенном выше примере вы можете сказать, что значение A имеет тот же эффект, что и виртуальная переменная.
Изменить: Это фактический ответ на ваш вопрос, но, увидев ваш пример кода, я заметил, что вы ищете разные типы в виртуальной переменной. Вы можете заменить double value
на объединение следующим образом:
union {
int intValue;
float floatValue;
} value
и присоединяется к нему так:
a->value.intValue = 3;
assert(a->value.floatValue == 3);
Обратите внимание, что по соображениям скорости я избегал этого.
Ответ 8
Это не поддерживается С++, поскольку это нарушает принципы инкапсуляции.
Ваши классы должны раскрывать и реализовывать общедоступный (возможно, виртуальный) интерфейс, который не говорит пользователям классов о внутренней работе вашего класса. Интерфейс должен описывать операции (и результаты), которые класс может выполнять на абстрактном уровне, а не как "установить эту переменную в X".
Ответ 9
Поскольку согласно стандарту C смещение поля внутри класса или структуры должно быть константой времени компиляции. Это также относится к обращению к полям базового класса.
Ваш пример не будет работать с виртуальными геттерами, поскольку для переопределения требуется подпись того же типа. Если это было необходимо, ваш виртуальный получатель должен был бы вернуться с алгебраическим типом, и код приема должен был бы проверяться во время выполнения, если он был ожидаемого типа.