Открытый и частный доступ для одних и тех же функций-членов - PullRequest
3 голосов
/ 02 февраля 2009

У меня есть класс (класс A), который предназначен для наследования другими классами, написанными другими людьми. У меня также есть другой класс (класс B), который также наследуется от A.

B должен получить доступ к некоторым функциям-членам A, к которым не должны обращаться другие наследующие классы.

Итак, эти функции-члены A должны быть открытыми для B, но закрытыми для других.

Как я могу решить эту проблему, не используя директиву друга?

Спасибо.

РЕДАКТИРОВАТЬ: Пример, почему мне это нужно.

class A
{
public:
  void PublicFunc()
  {
    PrivateFunc();
    // and other code
  }
private:
  virtual void PrivateFunc();
};

class B : public class A
{
private:
  virtual void PrivateFunc()
  {
    //do something and call A's PrivateFunc
    A::PrivateFunc(); // Can't, it's private!
  }
};

Ответы [ 6 ]

4 голосов
/ 02 февраля 2009

Вы не можете. Для этого и нужен друг.

Альтернативой может быть изменение дизайна / архитектуры вашей программы. Но для намеков на это мне нужно больше контекста.

3 голосов
/ 02 февраля 2009

То, что вы говорите: есть два набора подклассов A. Один набор должен иметь доступ, другой набор не должен. Неправильно иметь только одну марку подклассов (т. Е. B), «видеть» членов A.

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

(Повторное использование функциональности по наследству часто ставит вас в тупик с такими проблемами. Если вы собираетесь использовать повторное использование путем агрегации, вы можете обойти это.)

Предложение:

// separate the 'invisible' from the 'visible'.
class A_private_part {
protected: 
  int inherited_content();
public:
  int public_interface();          
};

class B_internal : public A_private_part {
};

class A_export : private A_private_part {
public:
    int public_interface() { A_private_part::public_interface(); }
};

// client code
class ClientClass : public A_export {
};

Но лучше было бы пойти путем агрегации и разделить текущее «А» на видимую и невидимую часть:

class InvisibleFunctionality {
};

class VisibleFunctionality {
};

class B {
    InvisibleFunctionality m_Invisible;
    VisibleFunctionality m_Visible;
};

// client code uses VisibleFunctionality only
class ClientClass {
    VisibleFunctionality m_Visible;
};
1 голос
/ 03 февраля 2009

Я считаю это интересным испытанием. Вот как бы я решил проблему:

class AProtectedInterface
{
public:
    int m_pi1;
};

class B;
class A : private AProtectedInterface
{
public:
    void GetAProtectedInterface(B& b_class);

    int m_p1;
};

class B : public A
{
public:
    B();
    void SetAProtectedInterface(::AProtectedInterface& interface);

private:
    ::AProtectedInterface* m_AProtectedInterface;
};

class C : public A
{
public:
    C();
};

C::C()
{
    m_p1 = 0;
//    m_pi1 = 0; // not accessible error
}

B::B()
{
    GetAProtectedInterface(*this);

    // use m_AProtectedInterface to get to restricted areas of A
    m_p1 = 0;
    m_AProtectedInterface->m_pi1 = 0;
}

void A::GetAProtectedInterface(B& b_class)
{
    b_class.SetAProtectedInterface(*this);
}

void B::SetAProtectedInterface(::AProtectedInterface& interface)
{
    m_AProtectedInterface = &interface;
}

Если вы собираетесь использовать этот тип шаблонов все время, вы можете уменьшить код, используя шаблоны.

template<class T, class I>
class ProtectedInterfaceAccess : public I
{
public:
    void SetProtectedInterface(T& protected_interface)
    {
        m_ProtectedInterface = &protected_interface;
    }

protected:
    T& GetProtectedInterface()
    {
        return *m_ProtectedInterface;
    }

private:
    T* m_ProtectedInterface;
};

template<class T, class I>
class ProtectedInterface : private T
{
public:
    void SetupProtectedInterface(I& access_class)
    {
        access_class.SetProtectedInterface(*this);
    }
};

class Bt;
class At : public ProtectedInterface <::AProtectedInterface, Bt>
{
public:
    int m_p1;
};

class Bt : public ProtectedInterfaceAccess<::AProtectedInterface, At>
{
public:
    Bt();
};

class Ct : public At
{
public:
    Ct();
};

Ct::Ct()
{
    m_p1 = 0;
    // m_pi1 = 0; // not accessible error
}

Bt::Bt()
{
    SetupProtectedInterface(*this);

    m_p1 = 0;
    GetProtectedInterface().m_pi1 = 0;
}
1 голос
/ 02 февраля 2009

Хорошо - если вы хотите именно то, что вы описали, тогда друг - лучшее решение. Каждый стандарт кодирования рекомендует не использовать friend , но там, где альтернативный дизайн более сложный - тогда, возможно, стоит сделать исключение.

Для решения проблемы без друга потребуется другая архитектура

Одним из решений может быть использование формы pImpl idiom , где «B» происходит от внутреннего объекта реализации, в то время как другие клиенты происходят от внешнего класса.

Другим может быть размещение дополнительного уровня наследования между «A» и «другими клиентами». Что-то вроде:

class A {
public:
  void foo ();
  void bar ();
};

class B : public A {  // OK access to both 'foo' and 'bar'
};

class ARestricted : private A {
public:
  inline void foo () { A::foo (); };    // Forwards 'foo' only
};

Однако, у этого решения все еще есть свои проблемы. «ARestricted» не может преобразовать в «A», так что это должно быть решено другим «получателем» для «A». Однако вы можете назвать эту функцию так, чтобы ее нельзя было вызвать случайно:

  inline A & get_base_type_A_for_interface_usage_only () { return *this; }

После попытки подумать о других решениях и предположив, что ваша иерархия должна быть такой, как вы описали, я рекомендую вам просто использовать друг !

РЕДАКТИРОВАТЬ: Итак xtofl предложил переименовать типы «A» в «AInternal» и «ARestricted» в «A».

Это работает, за исключением того, что я заметил, что «B» больше не будет «A». Тем не менее, AInternal может быть унаследован виртуально - и тогда «B» может происходить как от «AInternal», так и от «A»!

class AInternal {
public:
  void foo ();
  void bar ();
};

class A : private virtual AInternal {
public:
  inline void foo () { A::foo (); };    // Forwards 'foo' only
};

// OK access to both 'foo' and 'bar' via AInternal
class B : public virtual AInternal, public A {
public:
  void useMembers ()
  {
    AInternal::foo ();
    AInternal::bar ();
  }
};

void func (A const &);

int main ()
{
  A a;
  func (a);

  B b;
  func (b);
}

Конечно, теперь у вас есть виртуальные базы и множественное наследование! Хммм .... теперь, это лучше или хуже, чем одна друг декларация?

1 голос
/ 02 февраля 2009

Я думаю, что у вас есть большая проблема здесь. Ваш дизайн не кажется здоровым.

1) Я думаю, что конструкция «друга» проблематична с

2) если «друг» не то, что вам нужно, вам нужно пересмотреть свой дизайн.

Я думаю, что вам либо нужно сделать что-то, что просто выполняет свою работу, используя «друга», либо разработать более надежную архитектуру. Взгляните на некоторые шаблоны проектирования , я уверен, вы найдете что-то полезное.

РЕДАКТИРОВАТЬ:

После просмотра примера кода вам, безусловно, необходимо выполнить повторную архивацию. Класс A может не находиться под вашим контролем, так что это немного сложно, но, возможно, вы захотите, чтобы вы снова решили сделать класс B классом has-a вместо класса is-a.

public Class B
{
    B() 
    {

    }

    void someFunc()
    {
       A a; //the private functions is now called and a will be deleted when it goes out of scope
    }

};
0 голосов
/ 02 февраля 2009

Если я понимаю:

  • A будет разделено на подклассы другими разработчиками.
  • B будет разделено на подклассы другими разработчиками и наследуется от A.
  • A имеет несколько методов, которые вы не хотите, чтобы они были доступны внешним разработчикам через B.

Не думаю, что это можно сделать без помощи друга. Я не знаю, как сделать членов суперкласса доступными только для прямых наследников.

...