2015-12-22 4 views
1

Я пришел из Java, где не разрешено уменьшать модификаторы доступа в производных классах. Для instnace, следующий не компилировать Java:Вызов частной функции-члена в C++

public class A{ 
    public void foo(){ } 
} 

public class B extends A{ 
    @Override 
    private void foo(){ } //compile-error 
} 

Но, в C++ это хорошо:

struct A { 
    A(){ } 
    virtual ~A(){ } 
    A(A&&){ } 
public: 
    virtual void bar(){ std::cout << "A" << std::endl; } 
}; 

struct B : public A{ 
private: 
    virtual void bar(){ std::cout << "B" << std::endl; } 
}; 

int main() 
{ 
    A *a = new B; 
    a -> bar(); //prints B 
} 

DEMO

Где это может быть полезно? Кроме того, безопасно ли это сделать?

+1

Как в стороне, модификатор 'public' в структуре A ничего не делает, потому что все в структуре открыто по умолчанию. – MrEricSir

+2

Лично я бы не рекомендовал менять уровень доступа, поскольку я думаю, что это может ввести в заблуждение. Однако я не знаю причин, почему это будет считаться небезопасным, и я не знаю, почему это было бы полезно. Связанный, может быть полезно иметь частную виртуальную функцию, вызываемую не виртуальной публичной функцией. См. Идиому NVI (не виртуального интерфейса). –

+0

@JamesAdkison Это «небезопасно», потому что позволяет внешнему коду вызвать частную функцию напрямую, что противоречит всей точке частных функций. – immibis

ответ

3

Как вы заметили, спецификатор доступа работает на основе типа указателя, а не на основе динамического типа. Таким образом, указание частного в B в этом случае просто означает, что функции не могут быть доступны через указатель на B. Таким образом, это может быть полезно для хранения вещей в заблокированных случаях, когда клиент не должен использовать указатели на B (или создавать B в стеке). В основном, в тех случаях, когда конструктор B является закрытым, и вы создаете B (и, возможно, других детей из A) через фабрику, которая возвращает unique_ptr<A>. В этом случае вы можете просто указать все методы B как частные. В принципе это не позволяет клиенту «злоупотреблять» интерфейсом путем динамического литья unique_ptr вниз, а затем напрямую обращаться к интерфейсу B.

Я действительно не думаю, что это должно быть сделано; это более Java-y-подход, чем C++. В общем случае, если клиент хочет использовать производный объект в стеке, а не в куче через указатель базового класса, они должны быть в состоянии. Это дает лучшую производительность, и это легче рассуждать. Он также лучше работает в общем коде.

Редактировать: Я думаю, что я должен уточнить. Рассмотрим следующий код:

enum class Impl {FIRST, SECOND, THIRD}; 

unique_ptr<A> create(Impl i) { 
    ... 
} 

Предположим, что это единственный способ создать конкретные экземпляры, которые используют интерфейс A. Я мог бы желать, возможно, что производные классы являются чистыми деталями реализации. Например, я мог бы реализовать каждую из трех реализаций в другом классе, а затем решить, что два из трех могут быть объединены в один класс с разными параметрами и т. Д. Это не бизнес пользователя; их мир - это просто интерфейс A и функция create. Но теперь предположим, что пользователь бывает смотреть на источник и знает, что FIRST реализация реализуется с помощью B. Они хотят более высокую производительность, так что они делают это:

auto a = create(Impl::FIRST); 
auto b = dynamic_cast<B *>(a.get()); 
// Use b below, potentially avoiding vtable 

Если пользователь имеет код, как это, и вы избавляете или переименуйте класс B, их код сломается. Сделав все частные методы B, вы делаете b-указатель бесполезным для пользователя, гарантируя, что он использует интерфейс по назначению.

Как я уже говорил, я не особо защищаю это программирование на C++. Но могут быть ситуации, когда вам действительно нужны производные классы, чтобы быть чистыми деталями реализации; в тех случаях, когда изменение спецификатора доступа может помочь обеспечить его выполнение.

Смежные вопросы