Методы расширения в с ++ - PullRequest
       1

Методы расширения в с ++

52 голосов
/ 28 марта 2011

Я искал реализацию методов расширения в c ++ и натолкнулся на это обсуждение comp.std.c ++ , в котором упоминается, что polymorphic_map можно использовать для связанных методов с классом, но при условииссылка кажется мертвой.Кто-нибудь знает, на что ссылался этот ответ, или есть ли другой способ расширить классы способом, аналогичным методам расширения (возможно, путем некоторого использования mixins?).

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

Ответы [ 6 ]

69 голосов
/ 28 марта 2011

Разные языки подходят к разработке по-разному.В частности, C # и Java имеют сильную точку зрения в отношении ОО, которая приводит к всему, что является объектом мышления (C # здесь немного слабее).При таком подходе методы расширения предоставляют простой способ расширения существующего объекта или интерфейса для добавления новых функций.

В C ++ нет методов расширения, и они не нужны.При разработке C ++, забудьте, что все является объектной парадигмой, которая, кстати, ложна даже в Java / C # [*] .В C ++ используется другое мышление, есть объекты, и у объектов есть операции, которые являются неотъемлемой частью объекта, но есть и другие операции, которые образуют часть интерфейса и не обязательно должны быть частью класса.Херб Саттер должен прочитать Что в классе? , где автор защищает (и я согласен), что вы можете легко расширить любой данный класс с помощью простых свободных функций.

Как особыйВ простом примере стандартный шаблонный класс basic_ostream имеет несколько методов-членов для вывода содержимого некоторых примитивных типов, а затем он расширяется с помощью (также шаблонных) свободных функций, которые расширяют эту функциональность до других типов с помощью существующего открытого интерфейса.Например, std::cout << 1; реализован как функция-член, тогда как std::cout << "Hi"; - это свободная функция, реализованная в терминах других более простых членов.

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

[*] Все является не объектом.

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

Даже некоторые операции, которые реализованы как функции-члены, на самом деле не являются операциями над объектом.Рассмотрим добавление для Complex числового класса, как sum (или +) больше операций над первым аргументом, чем вторым?Почему a.sum(b); или b.sum(a), не должно ли быть sum( a, b )?

Превращение операций в методы-члены на самом деле приводит к странным эффектам, но мы к ним просто привыкли: a.equals(b); и b.equals(a); могут иметь совершенно разные результаты, даже если реализация equals полностью симметрична,(Рассмотрим, что происходит, когда a или b является нулевым указателем)

23 голосов
/ 29 марта 2011

Оператор использования подхода Boost Range Library | ().

r | filtered(p);

Я тоже могу написать обрезку для строки следующим образом.

#include <string>

namespace string_extension {

struct trim_t {
    std::string operator()(const std::string& s) const
    {
        ...
        return s;
    }
};

const trim_t trim = {};

std::string operator|(const std::string& s, trim_t f)
{
    return f(s);
}

} // namespace string_extension

int main()
{
    const std::string s = "  abc  ";

    const std::string result = s | string_extension::trim;
}
7 голосов
/ 20 января 2014

Это самая близкая вещь, которую я когда-либо видел к методам расширения в C ++.Лично мне нравится, как он может быть использован, и, возможно, он ближе всего к методам расширения на этом языке.Но есть некоторые недостатки:

  • Может быть сложно реализовать
  • Приоритет оператора иногда может быть не очень приятным, это может вызывать сюрпризы

Решение:

#include <iostream>

using namespace std;


class regular_class {

    public:

        void simple_method(void) const {
            cout << "simple_method called." << endl;
        }

};


class ext_method {

    private:

        // arguments of the extension method
        int x_;

    public:

        // arguments get initialized here
        ext_method(int x) : x_(x) {

        }


        // just a dummy overload to return a reference to itself
        ext_method& operator-(void) {
            return *this;
        }


        // extension method body is implemented here. The return type of this op. overload
        //    should be the return type of the extension method
        friend const regular_class& operator<(const regular_class& obj, const ext_method& mthd) {

            cout << "Extension method called with: " << mthd.x_ << " on " << &obj << endl;
            return obj;
        }
};


int main()
{ 
    regular_class obj;
    cout << "regular_class object at: " << &obj << endl;
    obj.simple_method();
    obj<-ext_method(3)<-ext_method(8);
    return 0;
}

Это не мое личное изобретение, недавно мой друг отправил его мне по почте, он сказал, что получил его из списка рассылки университета.

7 голосов
/ 28 марта 2011

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

В обсужденииОчень сложный обходной путь обеспечивается с помощью оператора (что, на мой взгляд, является плохой идеей).Я полагаю, что решение, представленное в мертвой ссылке, было более или менее похоже (поскольку оно основывалось на операторе |).

Это основано на способности делать более или менее то же самое, что иметод расширения с операторами.Например, если вы хотите перегрузить оператор ostream << для вашего нового класса Foo, вы можете сделать: </p>

class Foo {
    friend ostream &operator<<(ostream &o, const Foo &foo);
    // more things...
};

ostream &operator<<(ostream &o, const Foo &foo)
{
  // write foo's info to o
}

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

Самый похожий подход, который я могу придумать, будет означать созданиеРасширьте класс и создайте там свои новые методы.К сожалению, это означает, что вам нужно «адаптировать» ваши объекты:

class stringext {
public:
    stringext(std::string &s) : str( &s )
        {}
    string trim()
        {  ...; return *str; }
private:
    string * str;
};

И затем, когда вы захотите сделать это:

void fie(string &str)
{
    // ...
    cout << stringext( str ).trim() << endl;
}

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

0 голосов
/ 09 июля 2019

Чтобы подробнее узнать об ответе @Akira, operator| может использоваться для расширения существующих классов функциями, которые также принимают параметры.Вот пример, который я использую для расширения библиотеки Xerces XML с помощью функций поиска, которые можно легко объединить:

#pragma once

#include <string>
#include <stdexcept>

#include <xercesc/dom/DOMElement.hpp>

#define _U16C // macro that converts string to char16_t array

XERCES_CPP_NAMESPACE_BEGIN
    struct FindFirst
    {
        FindFirst(const std::string& name);
        DOMElement * operator()(const DOMElement &el) const;
        DOMElement * operator()(const DOMElement *el) const;
    private:
        std::string m_name;
    };

    struct FindFirstExisting
    {
        FindFirstExisting(const std::string& name);
        DOMElement & operator()(const DOMElement &el) const;
    private:
        std::string m_name;
    };

    inline DOMElement & operator|(const DOMElement &el, const FindFirstExisting &f)
    {
        return f(el);
    }

    inline DOMElement * operator|(const DOMElement &el, const FindFirst &f)
    {
        return f(el);
    }

    inline DOMElement * operator|(const DOMElement *el, const FindFirst &f)
    {
        return f(el);
    }

    inline FindFirst::FindFirst(const std::string & name)
        : m_name(name)
    {
    }

    inline DOMElement * FindFirst::operator()(const DOMElement &el) const
    {
        auto list = el.getElementsByTagName(_U16C(m_name));
        if (list->getLength() == 0)
            return nullptr;

        return static_cast<DOMElement *>(list->item(0));
    }

    inline DOMElement * FindFirst::operator()(const DOMElement *el) const
    {
        if (el == nullptr)
            return nullptr;

        auto list = el->getElementsByTagName(_U16C(m_name));
        if (list->getLength() == 0)
            return nullptr;

        return static_cast<DOMElement *>(list->item(0));
    }

    inline FindFirstExisting::FindFirstExisting(const std::string & name)
        : m_name(name)
    {
    }

    inline DOMElement & FindFirstExisting::operator()(const DOMElement & el) const
    {
        auto list = el.getElementsByTagName(_U16C(m_name));
        if (list->getLength() == 0)
            throw runtime_error(string("Missing element with name ") + m_name);

        return static_cast<DOMElement &>(*list->item(0));
    }

XERCES_CPP_NAMESPACE_END

Это можно использовать следующим образом:

auto packetRate = *elementRoot | FindFirst("Header") | FindFirst("PacketRate");
auto &decrypted = *elementRoot | FindFirstExisting("Header") | FindFirstExisting("Decrypted");
0 голосов
/ 12 июня 2019

Вы можете включить своего рода методы расширения для своего собственного класса / структуры или для определенного типа в некоторой области. См. Примерное решение ниже.

class Extensible
{
public:
    template<class TRes, class T, class... Args>
    std::function<TRes(Args...)> operator|
        (std::function<TRes(T&, Args...)>& extension)
    {
        return [this, &extension](Args... args) -> TRes
        {
            return extension(*static_cast<T*>(this), std::forward<Args>(args)...);
        };
    }
};

Тогда унаследуйте свой класс от этого и используйте как

class SomeExtensible : public Extensible { /*...*/ };
std::function<int(SomeExtensible&, int)> fn;
SomeExtensible se;
int i = (se | fn)(4);

Или вы можете объявить этот оператор в файле cpp или пространстве имен.

//for std::string, for example
template<class TRes, class... Args>
std::function<TRes(Args...)> operator|
    (std::string& s, std::function<TRes(std::string&, Args...)>& extension)
{
    return [&s, &extension](Args... args) -> TRes
    {
        return extension(s, std::forward<Args>(args)...);
    };
}

std::string s = "newStr";
std::function<std::string(std::string&)> init = [](std::string& s) {
    return s = "initialized";
};
(s | init)();

Или даже обернуть это в макрос (я знаю, это вообще плохая идея, тем не менее, вы можете):

#define ENABLE_EXTENSIONS_FOR(x) \
template<class TRes, class... Args> \
std::function<TRes(Args...)> operator| (x s, std::function<TRes(x, Args...)>& extension) \
{ \
    return [&s, &extension](Args... args) -> TRes \
    { \
        return extension(s, std::forward<Args>(args)...); \
    }; \
}

ENABLE_EXTENSIONS_FOR(std::vector<int>&);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...