2014-02-08 2 views
4

Речь идет об «защищенном», что объясняется следующим образом: «Когда класс наследует другой, члены производного класса могут обращаться к защищенным членам, унаследованным от базового класса». Тем не менее, посмотрите на этот код:Когда защита защищает слишком много

class Base { 
public: 
    Base(int m) : member(m){} 
protected: 
    int member; 
}; 

class Derived : public Base { // one of several subclasses 
public: 
    Derived(int m) : Base(m), value(10) {} 
    int diff(Base x){ 
    return value - x.member; 
    } 
private: 
    int value; 
}; 

Производная обращается «x.member», который защищен в его базовом классе, правильно? Но компилятор помещает ошибку, «Base :: member защищен». И, обдумав это на минуту, я должен был согласиться с компилятором.

И вот вопрос: Как это сделать, с минимальной потерей информации?

  1. Очевидно, что создание «участника» публично компилируется, но оно против первоначального намерения.
  2. Использование механизма «друга» в базе, чтобы подклассы могли получить доступ к «члену» (и все остальное, что является частным и защищенным) еще хуже (кроме немой привязки суперкласса к его собственным подклассам - кошмар обслуживания).
  3. В простом примере public int getMember() {return member; } будет приемлемым. Но если тип участника - это X *, то лучше всего сделать это публично const X * getMember() {...}.

Я что-то пропустил?

+1

Просто определите 'int diff (Derived x)' или нет? – mb84

+0

Также, читая ваши точки 1 и 3, можно сделать вывод, что смысл защиты-члена-члена состоит в том, что он может быть прочитан, но не изменен. Таким образом, метод get будет делать это хорошо. Также возможен публичный const-reference или const-pointer-to-const для члена, который инициализируется в списке конструкторов Base. Ваша точка 3 и «лучшее, что вы можете сделать ... ** const ** X *" зависит от того, что вы хотите: метод get возвращает значение в любом случае. Таким образом, вы не можете изменить значение члена типа X * с помощью 'X * get()'. Только то, на что это указывает. – mb84

+0

Как сделать функцию 'int diff (Derived d, Base b)' и сделать эту функцию «другом» обоих классов? – rozina

ответ

2

Вы можете использовать статический защищенный аксессор:

class Base { 
public: 
    Base(int m) : member(m){} 
private: 
    int member; 
protected: 
    static int GetMember(const Base &b) 
    { return b.member; } 
}; 

class Derived : public Base { // one of several subclasses 
public: 
    Derived(int m) : Base(m), value(10) {} 
    int diff(Base &x){ //beware of your slicing! 
    return value - GetMember(x); 
    } 
private: 
    int value; 
}; 

Теперь позвольте мне добавить свою идею о том, почему C++ контроль доступа работает таким образом ...

контроля доступа в C++ скрывается не о информации.Это около инкапсуляция. То есть, прямо говоря, вы отфильтровываете доступ к любому члену, который может сломать класс, если он используется неправильно.

В идеальном классе

  • общественные члены не могут быть использованы, чтобы сломать объект.
  • Частные члены знают, что они делают.

Как вы видите, в моей схеме есть небольшое место для защищенных членов:

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

И даже меньшее место для защищенных переменных-членов.

Так что сделайте вашу переменную приватной и напишите защищенный аксессуар. Аксессор должен быть статичным, чтобы его можно было использовать из производного объекта.

+0

«переменные не определяют правильный интерфейс»: плохие новости для 'std :: pair' ;-) Конечно, это не предназначено как базовый класс, поэтому все, что я действительно придираюсь, это то, должны ли вы указывать« переменные » не определяют надлежащий наследуемый интерфейс "или некоторые такие. –

+0

@SteveJessop: Хммм, папа выбор слов. Конечно, они могут быть частью интерфейса. Я имел в виду, что интерфейс, состоящий из переменных, неограничен, поэтому вы не можете заставить инвариант на них. Если это то, что вы хотите опубликовать; если нет, пойдите в частную; но защищенные переменные имеют мало смысла. Конечно, бывают случаи, когда они могут быть полезными (я их использовал), но не так много. – rodrigo

+0

Согласовано, 'pair' является специальным, потому что по определению его два члена независимы. Никакой классовый инвариант не связывает их. –

3

Вы можете сохранить защищенные атрибуты, добавить функции геттера, как вы упомянули. Что касается атрибута защищенного указателя, то getter должен удостовериться, что int (или const ref для большого объекта) возвращается, и вы можете сделать разницу в шаблоне функции, который принимает аргументы Derived и Base тогда (getters дают вам значения для расчет).

Защита атрибутов данных обеспечивает прямой доступ к защищенному атрибуту в производном классе. Вы пытались получить доступ к частному атрибуту другого объекта. Эта часть кода:

int diff(Base x){ 
    return value - x.member; 
    } 

будет эквивалентно написанию этого в main:

Base x; 
cout << x.member << endl; 

Вот пример того, как решить эту проблему с помощью опции 3 вы предложили себя, с производным класс PointerDerived, который использует указатели для хранения:

#include <iostream> 

class Base { 
public: 
    Base(int m) : member(m){} 

    int getMember() const 
    { 
     return member; 
    } 

protected: 
    int member; 
}; 

class Derived : public Base { // one of several subclasses 
public: 
    Derived(int m) : Base(m), value(10) {} 

    int getValue() const 
    { 
     std::cout << "protected = " << member << std::endl; 
     return value; 
    } 

private: 
    int value; 
}; 

class PointerDerived : public Base { // one of several subclasses 
public: 

    PointerDerived(int m) : Base(m), value(new int (10)) {} 

    int getValue() const 
    { 
     std::cout << "protected = " << member << std::endl; 
     return *value; 
    } 

    ~PointerDerived() 
    { 
     delete value; 
     value = nullptr; 
    } 

private: 
    int* value; 
}; 

template<typename Derived, typename Base> 
int diff(const Derived& d, const Base& b) 
{ 
    return d.getValue() - b.getMember(); 
} 

using namespace std; 

int main(int argc, const char *argv[]) 
{ 
    PointerDerived p(23); 
    Base q(1); 

    cout << diff(p, q) << endl; 

    return 0; 
} 

Компиляция программы с -std=c++11 из-за nullptr или изменения до NULL.

Вы создаете шаблон функции diff, поэтому вам не нужно перегружать его для каждого производного класса, и вы позволяете производному классу обрабатывать хранилище и доступ к нему, например, PointerDerived.

+0

Зачем использовать указатели для _int_? Зачем использовать геттеры? – Davidbrcz

+1

Некоторые из этого кода отвечают на вопрос, который не запрашивался. Я не уверен, что это поможет даже OP, не говоря уже о том, кто попытается найти ответ на исходный вопрос. – Dialecticus

+0

Я просто добавил int *, потому что OP спросил, есть ли X * в качестве защищенного элемента данных. Getters есть, потому что OP сказал, что это будет приемлемо для простого примера. diff - это функция, потому что геттеры должны убедиться, что PointerDerived возвращает int - для этого простого примера. Имеет ли это смысл? – tmaric