Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

[*] Все не объект.

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

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

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

Ответ 2

Подход к библиотеке расширенного диапазона использует оператор |().

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

Ответ 3

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

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

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

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

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

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

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

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

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

Ответ 4

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

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

Решение:

#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;
}

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

Ответ 5

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

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>&);

Ответ 6

Чтобы подробнее узнать о @Akira answer, 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");