2013-06-14 3 views
2

У меня есть некоторые проблемы с виртуальными классами и инкапсуляцией. Рассмотрим следующий минимальный пример программы на C++:Виртуальные интерфейсы и инкапсуляция

#include <iostream> 

class IConnection 
{ 
    public: 
     virtual void connect() = 0; 
     virtual std::string recv() = 0; 
     virtual void disconnect() = 0; 
     virtual ~IConnection() {} 
}; 


class ConcreteConnection: public IConnection 
{ 
    public: 
     ConcreteConnection(): m_connected(false) {} 
     void connect() { m_connected = true; } 
     std::string recv() { return "Received some text."; } 
     void disconnect() { m_connected = false; } 

    private: 
     bool m_connected; 

}; 

class Container 
{ 
    public: 
     Container() { m_connection = NULL; } 
     void SetConnection(IConnection *connection) { m_connection = connection; }; 
     void GetData() { std::cout << m_connection->recv() << std::endl; } 
     ~Container() { delete m_connection; } 

    private: 
     IConnection *m_connection; 
}; 

int main(void) 
{ 
    Container container; 
    ConcreteConnection *connection = new ConcreteConnection(); 

    container.SetConnection(connection); 
    container.GetData(); 

    return 0; 
} 

Этот простой пример прекрасно работает, но я не совсем доволен. Контейнер-объект должен владеть соединением, не беспокоясь о конкретной реализации интерфейса IConnection . Вот почему я создал объект ConcreteConnection вне контейнера. Что мне не нравится, так это то, что Мне нужно передать указатель или ссылку на соединение. Я хотел бы передать копию объекта-соединения, так что основная функция не имеет возможности манипулировать или удалять объект-соединение после передачи его в контейнер . Но, насколько я знаю, невозможно передать копию соединения , не сообщив контейнеру, к которому он относится, конкретную реализацию IConnection.

Итак, у вас есть идеи, как это решить? Возможно ли каким-либо образом передать копию объекта какой-либо функции без указания функции, для которой конкретная реализация интерфейса, к которому принадлежит объект?

Я относительно новым для обоих C++ и объектно-ориентированного программирования, так что не стесняйтесь и скажите мне, если мой класс структура совершенно неправильно и в этом случае не происходит в реальной жизни программный код (и как это должно работа вместо этого).

Заранее спасибо.

+2

На самом деле вы можете реализовать протокол копирования в рамках интерфейса подключения в качестве виртуального метода (скажем, 'clone' или 'copy'), который будет выполнять фактическую работу. Но в более общем плане, если вы хотите, чтобы объект «просачивался» в другие части вашей базы кода («основной» в вашем примере), вы можете реализовать шаблон инъекции зависимостей с помощью «фабричного» синглтона, цель которого заключается в создании и доставке экземпляра соединения. Вы разделили бы существование экземпляра на конечного владельца и метод фабрики, строящий его, и вы также избегаете необходимости в копии. – didierc

+0

Звучит неплохо! Подумайте об этом, чтобы я мог принять это. – lslah

+1

Я лично подобрал бы ответ Керрека С.Б., поскольку он решает проблему на C++, перемещая конструкцию объекта непосредственно в контейнере. Это именно то, что вам нужно, и оно также реализует шаблон инъекции (контейнер не знает, какой конкретный класс получает экземпляр).В моем решении это знание было ограничено фабричным методом, но вы все равно должны были убедиться, что экземпляр не поддерживается им каким-то образом (так как это было вашей первоначальной проблемой). Обратите внимание, что вы все равно можете поместить вызов 'make' в другое место, чем' main' (например, класс, предназначенный для установки приложения). – didierc

ответ

2

Вот как я мог бы написать это в современном C++:

#include <memory> 
#include <type_traits> 

class Container 
{ 
    std::unique_ptr<IConnection> ptr; 
    explicit Container(IConnection * p) : ptr(p) { } 
public: 
    template <typename T, typename ...Args> 
    static typename std::enable_if<std::is_base_of<IConnection, T>::value, Container>::type 
    make(Args &&... args) 
    { 
     return Container(new T(std::forward<Args>(args)...)); 
    } 
}; 

Использование:

int main() 
{ 
    auto c1 = Container::make<ConcreteConnection>(); 
    auto c2 = Container::make<TCPConnection>("127.0.0.1", 8080); 
    auto c3 = Container::make<LocalPipeConnection>("/tmp/pipe"); 
} 
+0

Сначала я не смог полностью понять ваш код, потому что я никогда не занимался «современным C++». Но после того, как Didierc указал, что ваш код на самом деле очень приятное решение, я провел некоторое исследование и теперь могу его понять. Большое спасибо за ваш ответ! Знаете ли вы о каком-либо хорошем введении в современный C++? Я читал о книге Андрея Александреску. Стоит ли это денег? Может быть, есть некоторые хорошие и бесплатные онлайн-уроки как первое введение? Может, мне стоит начать читать о шаблонах? – lslah

+1

@ Исла проверить [список книг] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list), который поддерживается здесь для C++ (возможно, вам стоит взглянуть на вкладка информации для C++, а также множество интересных ссылок). – didierc

0

Подумайте об этом так: все разные конкретные классы, которые наследуются от абстрактного класса, имеют разные члены и т. Д. Поэтому, если вы не сообщите компилятору, какой объект вы проходите, как он будет знать, сколько памяти выделять и т. д.?

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

+0

Но я думал, что так работают виртуальные классы/интерфейсы. Другим классам не нужно ничего знать о конкретной реализации интерфейса. И о вашей последней мысли: если моя основная функция делает копию, она все еще может манипулировать соединением, которое принадлежит контейнеру. Речь идет не о получении копии объекта, а о предотвращении того, что main может управлять соединением после передачи его в контейнер. – lslah