Как я могу симулировать интерфейсы в C ++?

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

T keyOne = ...;
T keyTwo = ...;

// At this point keyOne and keyTwo are different instances and 
// keyOne.equals(keyTwo) is true.

HashMap myMap = new HashMap();

myMap.push(keyOne, "Hello");

String s1 = (String) myMap.get(keyOne); // s1 is "Hello"
String s2 = (String) myMap.get(keyTwo); // s2 is "Hello" 
                                        // because keyOne equals keyTwo

mutate(keyOne);

s1 = myMap.get(keyOne); // returns "Hello"
s2 = myMap.get(keyTwo); // not found

Вышеуказанное верно, если ключ хранится как ссылка. Обычно в Java это так. Например, в .NET, если ключ является типом значения (всегда передается по значению), результат будет другим:

T keyOne = ...;
T keyTwo = ...;

// At this point keyOne and keyTwo are different instances 
// and keyOne.equals(keyTwo) is true.

Dictionary myMap = new Dictionary();

myMap.Add(keyOne, "Hello");

String s1 = (String) myMap[keyOne]; // s1 is "Hello"
String s2 = (String) myMap[keyTwo]; // s2 is "Hello"
                                    // because keyOne equals keyTwo

mutate(keyOne);

s1 = myMap[keyOne]; // not found
s2 = myMap[keyTwo]; // returns "Hello"

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

37
задан Bill the Lizard 4 June 2012 в 12:48
поделиться

7 ответов

Поскольку C ++ имеет множественное наследование, в отличие от C # и Java, да, вы можете создать серию абстрактных классов.

Что касается соглашения, это зависит от вас; однако я предпочитаю ставить перед именами классов букву I.

class IStringNotifier
{
public:
  virtual void sendMessage(std::string &strMessage) = 0;
  virtual ~IStringNotifier() { }
};

При сравнении C # и Java не о чем беспокоиться. По сути, у вас просто будут накладные расходы на наличие таблицы поиска для ваших функций или vtable, как и любое наследование с виртуальными методами.

39
ответ дан 27 November 2019 в 03:35
поделиться

На самом деле нет необходимости «моделировать» что-либо, поскольку это не значит, что в C ++ отсутствует что-либо, что Java может делать с интерфейсами.

Из указателя представления C ++ Java создает «искусственный "различие между интерфейсом и классом . Интерфейс - это просто класс , все методы которого абстрактны и не могут содержать никаких элементов данных.

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

В C ++ класс представляет собой класс и интерфейс - это класс . extends достигается общедоступным наследованием, а реализует также достигается общедоступным наследованием.

Наследование от нескольких неинтерфейсных классов может привести к дополнительным сложностям, но может быть полезно в некоторых ситуациях. Если вы ограничиваете себя наследованием классов не более чем от одного неинтерфейсного класса и любого количества полностью абстрактных классов, вы не столкнетесь с какими-либо другими трудностями, кроме как в Java (за исключением других различий C ++ / Java).

С точки зрения затрат на память и накладных расходов, если вы воссоздаете иерархию классов в стиле Java, то вы, вероятно, в любом случае уже оплатили стоимость виртуальных функций для своих классов. Учитывая, что вы все равно используете разные среды выполнения, есть

11
ответ дан 27 November 2019 в 03:35
поделиться

«Каковы последствия с точки зрения накладных расходов / производительности памяти?»

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

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

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

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

Что касается производительности, вызов виртуальной функции имеет немного больше накладных расходов, чем вызов невиртуальной функции, и, что более важно, вы можете Предположим, что он обычно (всегда?) не будет встроен. Добавление пустого базового класса обычно не приводит к добавлению кода для построения или уничтожения, поскольку пустой базовый конструктор и деструктор могут быть встроены в код конструктора / деструктора производного класса.

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

Пример кода:

#include <iostream>

// A is an interface
struct A {
    virtual ~A() {};
    virtual int a(int) = 0;
};

// B is an interface
struct B {
    virtual ~B() {};
    virtual int b(int) = 0;
};

// C has no interfaces, but does have a virtual member function
struct C {
    ~C() {}
    int c;
    virtual int getc(int) { return c; }
};

// D has one interface
struct D : public A {
    ~D() {}
    int d;
    int a(int) { return d; }
};

// E has two interfaces
struct E : public A, public B{
    ~E() {}
    int e;
    int a(int) { return e; }
    int b(int) { return e; }
};

int main() {
    E e; D d; C c;
    std::cout << "A : " << sizeof(A) << "\n";
    std::cout << "B : " << sizeof(B) << "\n";
    std::cout << "C : " << sizeof(C) << "\n";
    std::cout << "D : " << sizeof(D) << "\n";
    std::cout << "E : " << sizeof(E) << "\n";
}

Вывод (GCC на 32-битной платформе):

A : 4
B : 4
C : 8
D : 8
E : 12
7
ответ дан 27 November 2019 в 03:35
поделиться

В C ++ мы можем пойти дальше простых интерфейсов без поведения Java & co. Мы можем добавить явные контракты (как в Дизайн по контракту ) с шаблоном NVI.

struct Contract1 : noncopyable
{
    virtual ~Contract1();
    Res f(Param p) {
        assert(f_precondition(p) && "C1::f precondition failed");
        const Res r = do_f(p);
        assert(f_postcondition(p,r) && "C1::f postcondition failed");
        return r;
    }
private:
    virtual Res do_f(Param p) = 0;
};

struct Concrete : virtual Contract1, virtual Contract2
{
    ...
};
3
ответ дан 27 November 2019 в 03:35
поделиться

Интерфейсы в C ++ - это классы, которые имеют только чистые виртуальные функции. Например:

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
};

Это не смоделированный интерфейс, это интерфейс, подобный интерфейсам в Java, но не имеющий недостатков.

Например, вы можете добавлять методы и члены без негативных последствий:

class ISerializable
{
public:
    virtual ~ISerializable() = 0;
    virtual void  serialize( stream& target ) = 0;
protected:
    void  serialize_atomic( int i, stream& t );
    bool  serialized;
};

К соглашениям об именах ... в языке C ++ нет настоящих соглашений об именах. Поэтому выберите тот, который используется в вашей среде.

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

6
ответ дан 27 November 2019 в 03:35
поделиться

Кстати, в MSVC 2008 есть ключевое слово __ interface .

A Visual C++ interface can be defined as follows: 

 - Can inherit from zero or more base
   interfaces.
 - Cannot inherit from a base class.
 - Can only contain public, pure virtual
   methods.
 - Cannot contain constructors,
   destructors, or operators.
 - Cannot contain static methods.
 - Cannot contain data members;
   properties are allowed.

Эта функция специфична для Microsoft. Внимание: __ interface не имеет виртуального деструктора, который требуется, если вы удаляете объекты по его указателям на интерфейс.

1
ответ дан 27 November 2019 в 03:35
поделиться

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

Однако, если вы выполните что-то вроде оптимизации пустого базового класса, вы можете минимизировать это:

struct A
{
    void func1() = 0;
};

struct B: A
{
    void func2() = 0;
};

struct C: B
{
    int i;
};

Размер C будет двумя словами.

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

Однако, если вы выполните что-то вроде оптимизации пустого базового класса, вы можете минимизировать это:

struct A
{
    void func1() = 0;
};

struct B: A
{
    void func2() = 0;
};

struct C: B
{
    int i;
};

Размер C будет двумя словами.

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

Однако, если вы выполните что-то вроде оптимизации пустого базового класса, вы можете минимизировать это:

struct A
{
    void func1() = 0;
};

struct B: A
{
    void func2() = 0;
};

struct C: B
{
    int i;
};

Размер C будет двумя словами.

1
ответ дан 27 November 2019 в 03:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: