programing

C ++의 확장 메서드

nicescript 2021. 1. 13. 23:33
반응형

C ++의 확장 메서드


나는 C ++에서 확장 메소드의 구현을 찾고 있었고polymorphic_map , 클래스와 연관된 메소드에 사용될 수 있다는 것을 언급하는 이 comp.std.c ++ 토론에 이르렀 지만 제공된 링크가 죽은 것 같습니다. 그 대답이 무엇을 의미하는지 또는 확장 메서드와 유사한 방식으로 클래스를 확장하는 다른 방법이 있는지 아는 사람이 있습니까 (아마도 mixins 사용을 통해?).

표준 C ++ 솔루션은 무료 함수를 사용하는 것임을 알고 있습니다. 이것은 다른 무엇보다 호기심에서 나온 것입니다.


다른 언어는 다른 방식으로 개발에 접근합니다. 특히 C #과 Java는 모든 것이 객체 마인드로 이어지는 OO와 관련하여 강력한 관점을 가지고 있습니다 (C #은 여기서 조금 더 느슨합니다). 이러한 접근 방식에서 확장 메서드는 기존 개체 또는 인터페이스를 확장하여 새 기능을 추가하는 간단한 방법을 제공합니다.

C ++에는 확장 메서드가 없으며 필요하지도 않습니다. C ++를 개발할 때 모든 것이 객체 패러다임이라는 사실을 잊으십시오. 그건 그렇고 Java / C # [*] 에서도 거짓 입니다. C ++에서는 다른 사고 방식을 취하고 객체가 있으며 객체에는 본질적으로 객체의 일부인 작업이 있지만 인터페이스의 일부를 형성하고 클래스의 일부가 될 필요가없는 다른 작업도 있습니다. Herb Sutter가 읽어야하는 A must read by Herb Sutter is What 's In a Class? , 저자는 간단한 무료 기능으로 주어진 클래스를 쉽게 확장 할 수 있다고 옹호하고 동의합니다.

특별한 간단한 예로, 표준 템플릿 클래스 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이 널 포인터 일 때 어떤 일이 발생하는지 고려하십시오. )


Boost Range Library의 접근 방식은 operator | ()를 사용합니다.

r | filtered(p);

같은 방식으로 다음과 같이 string에 대한 trim을 작성할 수도 있습니다.

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

짧은 대답은 그렇게 할 수 없다는 것입니다. 긴 대답은 시뮬레이트 할 수 있지만 해결 방법으로 많은 코드를 만들어야한다는 것입니다 (실제로 우아한 솔루션은 없다고 생각합니다).

토론에서 operator-를 사용하여 매우 복잡한 해결 방법이 제공됩니다 (제 생각에는 나쁜 생각입니다). 나는 데드 링크에 제공된 솔루션이 operator |를 기반으로했기 때문에 덜 유사하다고 생각합니다.

이는 연산자를 사용하여 확장 방법과 거의 동일한 작업을 수행 할 수있는 능력에 기반합니다. 예를 들어 새 클래스 Foo에 대해 ostream의 operator <<를 오버로드하려면 다음을 수행 할 수 있습니다.

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

말했듯이 이것은 완벽하지 않으며 그런 종류의 완벽한 해결책이 존재하지 않는다고 생각합니다. 죄송합니다.


이것은 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;
}

이것은 제 개인적인 발명품이 아닙니다. 최근에 제 친구가 제게 우편으로 보냈어요. 그는 대학 메일 링리스트에서 받았다고 말했습니다.


@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");

자체 클래스 / 구조체 또는 일부 범위의 특정 유형에 대해 일종의 확장 메서드를 활성화 할 수 있습니다 . 아래의 대략적인 솔루션을 참조하십시오.

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

참조 URL : https://stackoverflow.com/questions/5463009/extension-methods-in-c

반응형