Расширение класса C ++ - PullRequest
       12

Расширение класса C ++

8 голосов
/ 25 октября 2009

Есть ли способ добавить новые методы в класс без изменения исходного определения класса (т. Е. Скомпилированного .lib, содержащего класс и соответствующий файл .h), как методы расширения класса C #?

Ответы [ 9 ]

17 голосов
/ 25 октября 2009

Нет. C ++ не имеет такой возможности.

Как уже упоминалось в других ответах, общие обходные пути:

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

Нет, вы не можете сделать это в C ++.

Если вы хотите добиться чего-то подобного, у вас есть 2 варианта,

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

Я предпочитаю подход делегирования.

5 голосов
/ 25 октября 2009

Методы расширения класса C # в основном являются синтаксическим сахаром. Вы получаете те же функциональные возможности со свободными функциями (то есть функциями со ссылкой или постоянной ссылкой на ваш класс в качестве их первого параметра). Так как это хорошо работает для STL, почему не для вашего класса?

3 голосов
/ 14 февраля 2013

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

var r = numbers.Where(x => x > 2).Select(x => x * x);

Если мы напишем это в C ++, используя свободную функцию, это будет выглядеть так:

auto r = select(where(numbers, [](int x) { return x > 2; }), [](int x) { return x * x; });

Мало того, что это трудно читать, но и трудно писать. Обычный способ решить эту проблему - создать так называемую функцию. Эти функции создаются путем перегрузки оператора канала | (который на самом деле является оператором or). Таким образом, приведенный выше код может быть написан так:

auto r = numbers | where([](int x) { return x > 2; }) | select([](int x) { return x * x; });

Что гораздо проще читать и писать. Многие библиотеки используют функцию pipable для диапазонов, но ее можно расширить и на другие классы. Boost использует его в своей библиотеке range , pstade духовка использует его, а также эта библиотека C ++ linq также использует его.

Если вы хотите написать свою собственную переносимую функцию, boost объяснит, как это сделать здесь . Другие библиотеки, однако, предоставляют функциональные адаптеры, чтобы упростить его. Pstade egg имеет адаптер для трубопровода , а linq предоставляет адаптер range_extension для создания функции трубопровода для диапазонов как минимум.

Используя linq, вы сначала создаете свою функцию как функциональный объект, подобный этому:

struct contains_t
{
    template<class Range, class T>
    bool operator()(Range && r, T && x) const
    { return (r | linq::find(x)) != boost::end(r); };
};

Затем вы инициализируете функцию с помощью статической инициализации следующим образом:

range_extension<contains_t> contains = {};

Тогда вы можете использовать свою функцию pipable следующим образом:

if (numbers | contains(5)) printf("We have a 5");
1 голос
/ 26 октября 2009

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

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

Например, std::find() или std::sort() являются частью интерфейса std::vector, даже если они не являются членами класса.

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

1 голос
/ 25 октября 2009

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

  • Создайте фабричную функцию, которая вызывается во всех местах, где требуется экземпляр класса, и возвращает указатель на экземпляр (Google для Design Patterns Factory , ...).
  • Создайте производный класс с нужными вам расширениями.
  • Заставьте функцию фабрики возвращать ваш производный класс вместо исходного класса.

Пример:


    class derivedClass: public originalClass { /* ... */};

    originalClass* createOriginalClassInstance()
    {
         return new derivedClass();
    }
  • Всякий раз, когда вам нужно получить доступ к расширениям, вам, конечно, необходимо привести оригинальное приведение к производному классу.

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

0 голосов
/ 14 сентября 2011

Вы не можете физически добавлять методы или данные в файл класса в двоичном виде. Однако вы можете добавить методы и данные (функциональность и состояние) к объектам этого класса, написав классы расширений. Это не просто и требует программирования на основе мета-объектного протокола и интерфейса. Вам нужно многое сделать для достижения этого в C ++, так как он не поддерживает Reflection из коробки. В такой реализации, когда вы запрашиваете интерфейс, реализованный вашим новым классом расширения через исходный указатель объекта класса, реализация мета-объекта возвращает этот указатель интерфейса через объект мета-класса для класса расширения, который он создает во время выполнения. Это то, как много настраиваемых (основанных на плагинах) программных приложений работают. Однако вы должны помнить, что для написания метаобъектов для всех классов необходимо использовать много других механизмов MOP, используя словари, в которых описаны отношения объектов, и дать правильные указатели интерфейса для исходных и расширенных объектов классов. CATIA V5 от Dassault Systemes написана в такой архитектуре, которая называется CAA V5, где вы можете расширять существующие компоненты, создавая новые классы расширений с желаемой функциональностью.

0 голосов
/ 25 октября 2009

Конечно, вы можете:


template <typename Ext>
class Class: public Ext { /* ... */ };

Это не значит, что это лучший подход.

0 голосов
/ 25 октября 2009

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

...