c ++中的扩展方法

时间:2011-03-28 17:54:16

标签: c++ c++11

我在c ++中搜索扩展方法的实现,并且this comp.std.c++ discussion提到polymorphic_map可以用于关联方法与类,但是,提供的链接似乎已经死了。有没有人知道答案是指的是什么,或者是否有另一种方法以类似的方式扩展类到扩展方法(可能通过一些mixins的使用?)。

我知道规范的C ++解决方案是使用自由函数;这更多是出于好奇而不是其他任何事情。

6 个答案:

答案 0 :(得分:67)

不同的语言以不同的方式处理发展。特别是对于OO而言,C#和Java具有强烈的观点,导致一切都是对象心态(C#在这里稍微宽松一些)。在该方法中,扩展方法提供了一种扩展现有对象或接口以添加新功能的简单方法。

C ++中没有扩展方法,也不需要它们。在开发C ++时,忘记一切都是对象范例 - 顺便说一下,即使在Java / C# [*] 中也是如此。在C ++中采用了不同的思维方式,有对象,并且对象具有本身就是对象的一部分的操作,但是还有其他操作构成了接口的一部分,不需要成为类的一部分。 Herb Sutter必读的是What's In a Class?,作者在其中辩护(并且我同意)您可以使用简单的自由函数轻松扩展任何给定的类。

作为一个特别简单的例子,标准模板化类basic_ostream有一些成员方法来转储某些基本类型的内容,然后通过(也模板化)自由函数增强它,将该功能扩展到其他使用现有的公共接口进行类型化。例如,std::cout << 1;实现为成员函数,而std::cout << "Hi";是根据其他更基本成员实现的自由函数。

C ++中的可扩展性是通过自由函数实现的,而不是通过向现有对象添加新方法的方式实现的。

[*]一切都 一个对象。

在给定的域中,将包含一组可以建模的实际对象以及可以应用于它们的操作,在某些情况下,这些操作将是对象的一部分,但在某些其他情况下它们不会。特别是你会发现声称一切都是对象的语言中的实用程序类而那些实用程序类只不过是一层试图隐藏这些方法的事实。 t属于任何特定对象。

甚至一些作为成员函数实现的操作也不是对象的真正操作。考虑添加Complex数字类,sum(或+)对第一个参数的操作比第二个参数更多?为什么a.sum(b);b.sum(a)不应该是sum( a, b )

强制操作成为成员方法实际上会产生奇怪的效果 - 但我们只是习惯了它们:a.equals(b);b.equals(a);可能会有完全不同的结果,即使equals的实现是完全对称的。 (考虑当ab是空指针时会发生什么)

答案 1 :(得分:22)

Boost Range Library的方法使用operator |()。

r | filtered(p);

我也可以用同样的方式为字符串编写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;
}

答案 2 :(得分:7)

简短的回答是你不能这样做。很长的答案是你可以模拟它,但要注意你必须创建大量的代码作为解决方法(实际上,我认为没有一个优雅的解决方案)。

在讨论中,使用operator-提供了一个非常复杂的解决方法(在我看来这是个坏主意)。我想死链接中提供的解决方案更不相似(因为它基于运算符|)。

这基于能够与运算符的扩展方法做或多或少相同的能力。例如,如果要重载ostream的运算符&lt;&lt;对于你的新课Foo,你可以这样做:

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

如上所述,这并不完美,我认为不存在那种完美的解决方案。 遗憾。

答案 3 :(得分:7)

这是我在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;
}

这不是我的个人发明,最近我的一位朋友把它寄给了我,他说他是从大学邮件列表中得到的。

答案 4 :(得分:1)

要详细说明@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");

答案 5 :(得分:0)

您可以为自己的类/结构或某些范围内的某些特定类型启用 kinda 扩展方法。请参阅下面的大致解决方案。

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

然后从中继承您的课程并使用like

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