Использование декларации для перегруженной унаследованной функции с приватной доступностью - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть класс, который выглядит примерно так:

class A
{
public:
    void foo(int arg) { foo(arg, false); }
private:
    void foo(int arg, bool flag) {}
};

Он построен таким образом, потому что я хочу, чтобы аргумент флага foo был ложным только при вызове извне A. Я хочу унаследовать это конфиденциально, но разрешить звонить foo:

class B : private A
{
public:
    using A::foo;
};

Однако это терпит неудачу, потому что объявление использования пытается перенести все перегрузки foo в область действия, включая частную, которую компилятор справедливо отклоняет.

Это не сложно исправить, я тоже могу:

  1. Изменить доступность A::foo(int, bool) на защищенную или общедоступную
  2. Наследовать A публично;
  3. Измените имя A::foo(int, bool), чтобы объявление использования не пыталось перенести его в область действия

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

Но не похоже, что это необходимо. Почему объявление using пытается перенести недоступные методы в область видимости? Этот конкретный случай просто не охватывается стандартом? Есть ли способ исправить это, кроме методов, которые я перечислил?

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Я нашел следующую выдержку из «Эффективного C ++» Скотта Мейера, которая связана с вашим затруднительным положением (с добавлением акцента):

Пункт 33: Избегайте сокрытия унаследованных имен.
...
Это означает, что если вы наследуете от базового класса с перегруженными функциями и вы хотите переопределить или переопределить только некоторые из них, вам нужно включить объявление об использовании для каждого имени, которое вы в противном случае скрывали бы. Если вы этого не сделаете, некоторые имена, которые вы хотели бы унаследовать, будут скрыты.
...

Вполне возможно, что вы иногда не захотите наследовать все функции из ваших базовых классов. При публичном наследовании это должно никогда не будет так, потому что, опять же, это нарушает публичное наследство связь между базовыми и производными классами. (Вот почему использование описанные выше объявления находятся в публичной части производного класса: names которые являются публичными в базовом классе, также должны быть публичными в публичном производный класс.)

Однако при частном наследовании имеет смысл. Например, предположим, что Derived в частном порядке наследует от Base, и единственная версия функции, которую Derived хочет унаследовать, это один не принимает никаких параметров. Использование объявления здесь не поможет, потому что объявление использования делает все унаследованные функции с заданным имя, видимое в производном классе .
Нет, это касается другой техники, а именно простой функции пересылки:

class Base {
public:
   virtual void mf1() = 0;
   virtual void mf1(int);
... // as before
};
class Derived: private Base {
public:
   virtual void mf1() // forwarding function; implicitly
   { 
      Base::mf1(); } // inline 
   }; 
}
0 голосов
/ 04 сентября 2018

Вы также можете переопределить нужную перегрузку и передать ее аргумент функции в A:

class B : private A
{
public:
    void foo(int arg) { A::foo(arg); }
};

Объявление использования просто слишком тупо в этом случае. Он добавляет функцию names в область производного класса. И когда имя относится к чему-то частному, оно задыхается. Он не может отличить перегрузки. Стандарт требует, чтобы имена, введенные объявлением использования, были доступны:

[namespace.udecl] / 17

В объявлении использования, которое не называет конструктор, все члены набор представленных объявлений должен быть доступен. В using-декларатор, который называет конструктор, без проверки доступа выполнила. В частности, если производный класс использует использование-декларатор чтобы получить доступ к члену базового класса, имя члена должно быть доступны. Если имя является именем перегруженной функции-члена, то все названные функции должны быть доступны. Члены базового класса Упомянутый декларатором об использовании должен быть виден в хотя бы один из прямых базовых классов того класса, где Указатель использования указан.


Функция пересылки также может быть настроена. Так что не нужно будет переопределять каждую функцию, которую они хотят выставлять индивидуально.

class B : private A
{
public:
    template<typename ...Args>
    void foo(Args ...args) { A::foo(args...); }
};

Это «ловушка для всех», как и в объявлении using, за исключением того, что спецификатор доступа проверяется только при создании экземпляра шаблона, то есть при вызове функции. Таким образом, шаблон будет неправильно сформирован в зависимости от области его применения и от того, доступен ли элемент в A.

...