Здорова ли C ++ дружба среди сверстников? - PullRequest
3 голосов
/ 23 февраля 2009

Хотя классовая дружба является одним из последних курортов C ++, имеет ли смысл этот шаблон?

class Peer
{
public:
    friend class Peer;
    void GetSecret(const Peer& other)
    {
        const std::string& secret = other.GiveSecret();
        std::cout << secret << std::endl;
    }

private:
    const std::string& GiveSecret() const
    {
        return "secrety stuff";
    }
};

int main(int argc, char* argv[])
{
    Peer peerA;
    Peer peerB;
    peerA.GetSecret(peerB);
    return 0;
}

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

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

Ответы [ 8 ]

18 голосов
/ 23 февраля 2009

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

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

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

2 голосов
/ 23 февраля 2009

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

Рассмотрим альтернативу, когда вы создаете метод доступа к вашим личным данным. Если вы сделаете это, то вы фактически предоставите доступ к личным данным всех клиентов, а не только к ограниченному набору классов / функций, объявленных как друзья. Если только один метод в вашем классе друзей собирается получить доступ к вашей внутренней инкапсуляции, он уменьшился на столько же, как если бы вы предоставили публичный метод доступа. Тем не менее, результатом предоставления метода открытого доступа будет то, что гораздо больше клиентов будут использовать метод.

Для каждого класса приложения, который мы пишем, существует теневой класс, который является модульным тестом классов. Класс модульного теста является другом класса приложения, так как ему часто требуется вызывать методы класса, а затем проверять внутренние компоненты класса или вызывать закрытые методы. Будучи другом класса, инкапсуляция поддерживается.

Во всяком случае, для хорошего обсуждения смотрите здесь: http://www.ddj.com/cpp/184401197

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

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

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

Например:

// This class defines an interface that allows selected classes to
// manipulate otherwise private data.
class SharedData
{
public:
    // Set some shared data.
    virtual void SettorA(int value) = 0;

    // Get some shared data.
    virtual bool GettorB(void) const;
};


// This class does something with the otherwise private data.
class Worker
{
public:
    void DoSomething(SharedData & data)
    {
        if (data.GettorB() == true)
        {
            data.SettorA(m_id);
        }
    }

private:
    int m_id;
};

// This class provides access to its otherwise private data to
// specifically selected classes.  In this example the classes
// are selected through a call to the Dispatch method, but there
// are other ways this selection can be made without using the
// friend keyword.
class Dispatcher
    : private SharedData
{
public:
    // Get the worker to do something with the otherwise private data.
    void Dispatch(Worker & worker)
    {
        worker.DoSomething(*this);
    }

private:
    // Set some shared data.
    virtual void SettorA(int value)
    {
        m_A = value;
    }

    // Get some shared data.
    virtual bool GettorB(void) const
    {
        return (m_B);
    }

    int    m_A;
    bool   m_B;
};

В этом примере SharedData - это интерфейс, который определяет, что можно сделать с данными, то есть что можно установить, а что только для получения. Worker - это класс, которому разрешен доступ к этому специальному интерфейсу. Dispatcher реализует интерфейс конфиденциально, поэтому доступ к экземпляру Dispatcher не дает вам доступа к специальным общим данным, но Dispatcher имеет метод, который позволяет работникам получать доступ.

0 голосов
/ 23 февраля 2009

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

class Sub
    : public Base
{
public:
    Sub(const std::string & name)
        : Base(),
          m_name(name)
    {
    }

    Sub(const Sub & src)
        : Base(src),
          m_id(src.m_name)
    {
    }

    Sub & operator=(const Sub & rhs)
    {
        if (&rhs != this)
        {
            Base::operator=(rhs);

            m_name = rhs.m_name;
        }

        return (this);
    }

protected:
    virtual Debug(void)
    {
        std::cout << "Sub [m_name = " << m_name << "]" << std::endl
                  << "+- ";

        Base::Debug();
    }

private:
    std::string m_name;
};

class Base
{
public:
    Base(int id)
        : m_id(id)
    {
    }

    Base(const Base & src)
        : m_id(src.m_id)
    {
    }

    Base & operator=(const Base & rhs)
    {
        if (&rhs != this)
        {
            m_id = rhs.m_id;
        }

        return (this);
    }

protected:
    virtual Debug(void)
    {
        std::cout << "Base [m_id = " << m_id << "]" << std::endl;
    }

private:
    int m_id;
};

В этом примере подкласс может создавать экземпляры своих базовых членов как в конструкторе копирования, так и в операторе присваивания. Даже чрезмерно надуманный метод Debug работает, даже не имея доступа к закрытым членам базового класса.

0 голосов
/ 23 февраля 2009

Не здоров, это нарушает лучшие практики инкапсуляции

Ну да и нет.

Он может нарушить инкапсуляцию, только если вы не используете методы для доступа к нужным вам данным.

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

0 голосов
/ 23 февраля 2009

имеет ли смысл этот шаблон

Не очень. Можете ли вы уточнить?

  • Вы создаете дружбу, как правило, среди разных классов. Это синтаксически верно. Почему Пир был другом Пира?
  • Частные функции могут использоваться только функциями-членами. Вызывать их из кода клиента неверно.

Что касается вопроса, нарушает ли дружба инкапсуляцию или нет, взгляните на FAQ .

Извините, Роберт, неправильно прочитал имена.

0 голосов
/ 23 февраля 2009

Не здоров, это нарушает лучшие практики инкапсуляции

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...