繁体   English   中英

c++中的扩展方法

[英]Extension methods in c++

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

我知道规范的 C++ 解决方案是使用免费功能; 这更多是出于好奇。

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

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

作为一个特别简单的示例,标准模板化类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的实现是完全对称的,也可能有完全不同的结果。 (考虑当ab是空指针时会发生什么)

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

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

在讨论中,使用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");

您可以为您自己的类/结构或某些范围内的某些特定类型启用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>&);

这种语法糖在 C++ 中不可用,但您可以定义自己的命名空间并编写纯 static 类,使用const引用作为第一个参数。

例如,我正在努力使用 STL 实现一些数组操作,我不喜欢语法,我已经习惯了 JavaScript 的函数式数组方法工作方式。

因此,我使用其中的 class vector创建了自己的命名空间wh ,因为这是我期望使用这些方法的 class,结果如下:

//#ifndef __WH_HPP
//#define __WH_HPP

#include <vector>
#include <functional>
#include <algorithm>

namespace wh{
    template<typename T>
    class vector{
        public:
        static T reduce(const std::vector<T> &array, const T &accumulatorInitiator, const std::function<T(T,T)> &functor){
            T accumulator = accumulatorInitiator;

            for(auto &element: array)   accumulator = functor(element, accumulator);

            return accumulator;
        }

        static T reduce(const std::vector<T> &array, const T &accumulatorInitiator){
            return wh::vector<T>::reduce(array, accumulatorInitiator, [](T element, T acc){return element + acc;});
        }

        static std::vector<T> map(const std::vector<T> &array, const std::function<T(T)> &functor){
            std::vector<T> ret;

            transform(array.begin(), array.end(), std::back_inserter(ret), functor);

            return ret;
        }

        static std::vector<T> filter(const std::vector<T> &array, const std::function<bool(T)> &functor){
            std::vector<T> ret;

            copy_if(array.begin(), array.end(), std::back_inserter(ret), functor);

            return ret;
        }

        static bool all(const std::vector<T> &array,  const std::function<bool(T)> &functor){
            return all_of(array.begin(), array.end(), functor);
        }

        static bool any(const std::vector<T> &array, const std::function<bool(T)> &functor){
            return any_of(array.begin(), array.end(), functor);
        }
    };
}

//#undef __WH_HPP

我不会继承它,也不会用它组合 class,因为我从来没有能够在没有任何副作用的情况下和平地做到这一点,但我想出了这个,只是const引用。

当然,问题是为了使用这些 static 方法,您必须编写极其冗长的代码:

int main()
{
    vector<int> numbers = {1,2,3,4,5,6};
    numbers = wh::vector<int>::filter(numbers, [](int number){return number < 3;});
    numbers = wh::vector<int>::map(numbers,[](int number){return number + 3;});
    for(const auto& number: numbers)    cout << number << endl;


    return 0;
}

如果只有语法糖可以让我的 static 方法具有某种更常见的语法,例如:

myvector.map([](int number){return number+2;}); //...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM