Контекст :
Наследование защищенных и открытых членов класса является фундаментальной концепцией объектно-ориентированного программирования.Приведенный ниже тривиальный пример иллюстрирует часто встречающуюся ситуацию, в которой класс CDerived
наследует все открытые члены класса CBase
и добавляет 1 дополнительную функцию без изменения , явного повторного выделения или повторного определения любого изоткрытые члены класса CBase
.
#include <stdio.h>
class CBase
{
public:
char Arr[32];
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr)-2];
}
};
class CDerived : public CBase
{
public:
int FnSum(void) {
return Fn1() + Fn2();
}
};
int main(void)
{
CDerived ddd;
printf("%d\n", ddd.Fn1());
printf("%d\n", ddd.Fn2());
printf("%d\n", ddd.FnSum());
return (int)ddd.Arr[0];
};
Приведенный выше код без проблем компилируется на всех основных компиляторах.
Однако, если вы хотите " templatize"этот код, например: путем параметризации размера массива Arr
все открытые члены из CBase
шаблона класса становятся невидимыми для шаблона класса CDerived
на компиляторах, которые соответствуют последнему стандарту C ++.
Ниже приведен код ошибки:
#include <stdio.h>
template <unsigned int BYTES>
class CBase
{
public:
char Arr[BYTES];
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr)-2];
}
};
template <unsigned int BYTES>
class CDerived : public CBase<BYTES>
{
public:
int FnSum(void) {
return Fn1() + Fn2() + Arr[0]; // ERRORs: identifiers "Fn1" and "Fn2" and "Arr" are NOT found !
}
};
int main(void)
{
CDerived<32> ddd;
printf("%d\n", ddd.Fn1()); //No error here
printf("%d\n", ddd.Fn2()); //No error here
printf("%d\n", ddd.FnSum());
return (int)ddd.Arr[0]; //No error here
}
См .:
MSVC v19.10: https://godbolt.org/g/eQKDhb
ICC v18.0.0: https://godbolt.org/g/vBBEQC
GCC v8.1: https://godbolt.org/g/GVkeDh
Существует 4 решения этой проблемы:
Решение # 1 : префикс всехссылки на членов шаблона класса CBase
(дажеic), с CBase<BYTES>::
следующим образом:
int FnSum(void) {
return CBase<BYTES>::Fn1() + CBase<BYTES>::Fn2() + CBase<BYTES>::Arr[0];
}
См .:
MSVC v19.10: https://godbolt.org/g/48ZJrj
ICC v18.0.0: https://godbolt.org/g/BSPcSQ
GCC v8.1: https://godbolt.org/g/Vg4SZM
Решение # 2 : добавьте префикс ко всем ссылкам на элементы шаблона класса CBase
(даже к общедоступным), например this->
:
int FnSum(void) {
return this->Fn1() + this->Fn2() + this->Arr[0];
}
См .:
MSVC v19.10: https://godbolt.org/g/oBs6ud
ICC v18.0.0: https://godbolt.org/g/CWgJWu
GCC v8.1: https://godbolt.org/g/Gwn2ch
Solution # 3 : Добавьте один оператор using
внутри шаблона класса CDerived
для каждого члена CBase
(даже открытого), на который ссылается CDerived
, например:
using CBase<BYTES>::Arr;
using CBase<BYTES>::Fn1;
using CBase<BYTES>::Fn2;
См .:
MSVC v19.10: https://godbolt.org/g/gJT8cX
ICC v18.0.0: https://godbolt.org/g/1RK84A
GCC v8.1: https://godbolt.org/g/d8kjFh
Решение # 4 : Отключите строгое соответствие стандарту C ++, включив «разрешающий» режим в настройках компилятора, например:
Для MSVC v19.10 снимите переключатель /permissive-
,см .: https://godbolt.org/g/Yxw89Y
Для ICC v18.0.0 добавить переключатель -fpermissive
, см .: https://godbolt.org/g/DwuTb4
Для GCC v8.1 добавить переключатель -fpermissive
, see: https://godbolt.org/g/DHGBpW
MSVC ПРИМЕЧАНИЕ. Согласно этой статье , по умолчанию параметр /permissive-
установлен в новых проектах, созданных Visual Studio2017 v15.5 (компилятор MSVC v19.11) и более поздние версии.Он не устанавливается по умолчанию в более ранних версиях, включая последнюю версию MSVC Compiler Explorer от Godbolt.org v19.10.
GCC ПРИМЕЧАНИЕ. Даже с переключателем компилятора -fpermissive
компилятору GCC v8.1 по-прежнему нужен оператор using CBase<BYTES>::Arr;
внутри класса CDerived
(... или одно из других решений), чтобы сделать публичный массив Arr
видимым внутри шаблона класса CDerived
.... но не нужно ничего дополнительного, чтобы сделать видимыми функции Fn1()
и Fn2()
.
MSVC Non-Solution : Согласно В этой статье и В этой статье ошибка компиляции в MSVC возникает из-за двухфазного поиска имен, включаемого в соответствии со стандартным режимом C ++ (*Опция 1130 *).
Кроме того, согласно бывшей статье : " Опция /permissive-
неявно устанавливает соответствующее поведение компилятора двухфазного поиска, но его можно переопределить с помощью /Zc:twoPhase-
switch".
Однако добавление двухпереключение компилятора /permissive- /Zc:twoPhase-
не приводит к компиляции «шаблонного» кода проблемы в MSVC v19.14 без дополнений, описанных в решении № 1, № 2 или № 3.
MSVC v19.14: https://godbolt.org/z/BJlyA8
Подробнее см. в этой записи .
Проблемы с вышеуказанными решениями :
Решение № 4не переносимый и отрывается от стандарта C ++.Это также ГЛОБАЛЬНОЕ решение (глобальное переключение) для локальной проблемы - обычно плохая идея.Переключатель компилятора, который влияет только на часть кода (например, #pragma NOtwoPhase
), не существует.
Решение № 1 имеет непреднамеренный побочный эффект подавления виртуальных вызовов, поэтому оно не применимо в общем случае.
Оба решения № 1 и № 2 требуют много подробных дополнений к коду.Это приводит к вздутию исходного кода , которое не добавляет никакой новой функциональности .Например, если шаблон класса CDerived
добавляет только 2 функции к классу CBase
, который содержит 5 открытых функций и 1 переменную-член, на которые ссылаются несколько раз в CDerived
, Решение № 1 требует 14 подробного кодаизменения / дополнения в производном классе, которые выглядят так:
#include <stdio.h>
template <unsigned int BYTES>
class CBase
{
public:
char Arr[BYTES];
CBase() {
for (size_t i=1; i<sizeof(Arr); i++)
Arr[i] = Arr[i-1]+(char)i;
}
int Fn1(void) {
return Arr[1] ^ Arr[sizeof(Arr)-1];
}
int Fn2(void) {
return Arr[2] ^ Arr[sizeof(Arr) - 2];
}
int Fn3(void) {
return Arr[3] ^ Arr[sizeof(Arr) - 3];
}
int Fn4(void) {
return Arr[4] ^ Arr[sizeof(Arr) - 4];
}
int Fn5(void) {
return Arr[5] ^ Arr[sizeof(Arr) - 5];
}
};
template <unsigned int BYTES>
class CDerived : public CBase<BYTES>
{
public:
int FnSum(void) {
return CBase<BYTES>::Fn1() +
CBase<BYTES>::Fn2() +
CBase<BYTES>::Fn3() +
CBase<BYTES>::Fn4() +
CBase<BYTES>::Fn5() +
CBase<BYTES>::Arr[0] +
CBase<BYTES>::Arr[1] +
CBase<BYTES>::Arr[2];
}
int FnProduct(void) {
return CBase<BYTES>::Fn1() *
CBase<BYTES>::Fn2() *
CBase<BYTES>::Fn3() *
CBase<BYTES>::Fn4() *
CBase<BYTES>::Fn5() *
CBase<BYTES>::Arr[0] *
CBase<BYTES>::Arr[1] *
CBase<BYTES>::Arr[2];
}
};
int main(void)
{
CDerived<32> ddd;
printf("%d\n", ddd.FnSum());
printf("%d\n", ddd.FnProduct());
return (int)ddd.Arr[0];
}
В реальной жизни шаблон базового класса может содержать ~ 50 функций и множество переменных, на которые ссылаются несколько раз в шаблоне производного класса., что требует сотен таких повторных правок!
Должен быть лучший способ ...
Решение № 3 требует меньше работы, поскольку не требует поиска и префикса КАЖДОЙ ССЫЛКИ к элементу CBase
в коде CDerived
.CBase
члены, которые используются CDerived
, должны быть "повторно объявлены" с помощью оператора using
только один раз , независимо от того, сколько раз эти члены используются / упоминаются в CDerived
код.Это экономит много бессмысленного поиска и ввода.
К сожалению, такого общего оператора, как using CBase<BYTES>::*
, который делает все защищенные и открытые члены видимыми в шаблоне производного класса, не существует.
ВОПРОС :
Есть ли менее подробное портативное решение этой проблемы?например, решение № 5 ...