Расширение Класса С++

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

9 ответов


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

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

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

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

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

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

Я предпочитаю подход делегации.


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


в 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; });

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

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

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

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

используя 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");

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

  • создайте заводскую функцию, которая вызывается во всех местах, где требуется экземпляр класса, и возвращает указатель на экземпляр (google for Фабрика Шаблонов Дизайна, ...).
  • создать производный класс с нужными расширениями.
  • сделайте функцию factory возвращающей производный класс вместо исходного класса.

пример:


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

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

это примерно то, как реализовать метод "наследовать", предложенный Glen. Метод "wrapper class с тем же интерфейсом" Глена также очень хорош из теоретическая точка зрения, но имеет несколько иные свойства, что делает ее менее вероятной для работы в вашем случае.


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

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

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

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


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


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


конечно, можно:


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

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