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
'programing' 카테고리의 다른 글
Typescript가 이름 창 또는 문서를 찾을 수 없습니다. (0) | 2021.01.13 |
---|---|
Nginx 프록시 Amazon S3 리소스 (0) | 2021.01.13 |
-awakeFromNib 또는 -viewDidLoad를 사용해야합니까? (0) | 2021.01.13 |
Java에 포인터가 있습니까? (0) | 2021.01.13 |
csproj의 오류-중복 항목 (0) | 2021.01.13 |