简体   繁体   English

c++中的扩展方法

[英]Extension methods in c++

I was searching for an implementation of extension methods in c++ and came upon this comp.std.c++ discussion which mentions that polymorphic_map can be used to associated methods with a class, but, the provided link seems to be dead.我在 c++ 中搜索扩展方法的实现,并遇到了这个 comp.std.c++ 讨论,其中提到polymorphic_map可以用于与 class 关联的方法,但是,提供的链接似乎已经死了。 Does anyone know what that answer was referring to, or if there is another way to extend classes in a similar manner to extension methods (perhaps through some usage of mixins?).有谁知道那个答案指的是什么,或者是否有另一种方法以类似于扩展方法的方式扩展类(可能通过使用 mixins?)。

I know the canonical C++ solution is to use free functions;我知道规范的 C++ 解决方案是使用免费功能; this is more out of curiosity than anything else.这更多是出于好奇。

Different languages approach development in different ways. 不同语言以不同方式处理发展。 In particular C# and Java have a strong point of view with respect to OO that leads to everything is an object mindset (C# is a little more lax here). 特别是对于OO而言,C#和Java有一个强烈的观点,导致一切都是一个对象心态(C#在这里稍微宽松一点)。 In that approach, extension methods provide a simple way of extending an existing object or interface to add new features. 在该方法中,扩展方法提供了一种扩展现有对象或接口以添加新功能的简单方法。

There are no extension methods in C++, nor are they needed. C ++中没有扩展方法,也不需要它们。 When developing C++, forget the everything is an object paradigm --which, by the way, is false even in Java/C# [*] . 在开发C ++时,忘记一切都是一个对象范例 - 顺便说一下,即使在Java / C# [*]中也是如此 A different mindset is taken in C++, there are objects, and the objects have operations that are inherently part of the object, but there are also other operations that form part of the interface and need not be part of the class. 在C ++中采用了不同的思维方式,有对象,并且对象具有本身就是对象的一部分的操作,但是还有其他操作构成了接口的一部分,不需要成为类的一部分。 A must read by Herb Sutter is What's In a Class? Herb Sutter必须阅读的内容是什么? , where the author defends (and I agree) that you can easily extend any given class with simple free functions. ,作者辩护(并且我同意)您可以使用简单的自由函数轻松扩展任何给定的类。

As a particular simple example, the standard templated class basic_ostream has a few member methods to dump the contents of some primitive types, and then it is enhanced with (also templated) free functions that extend that functionality to other types by using the existing public interface. 作为一个特别简单的示例,标准模板化类basic_ostream有一些成员方法来转储某些基本类型的内容,然后通过使用现有公共接口将(也是模板化的)自由函数扩展到其他类型来增强它们。 。 For example, std::cout << 1; 例如, std::cout << 1; is implemented as a member function, while std::cout << "Hi"; 实现为成员函数,而std::cout << "Hi"; is a free function implemented in terms of other more basic members. 是一个免费的功能,是根据其他更基本的成员实现的。

Extensibility in C++ is achieved by means of free functions, not by ways of adding new methods to existing objects. C ++中的可扩展性是通过自由函数实现的,而不是通过向现有对象添加新方法的方式实现的。

[*] Everything is not an object. [*]一切都不是对象。

In a given domain will contain a set of actual objects that can be modeled and operations that can be applied to them, in some cases those operations will be part of the object, but in some other cases they will not. 在给定的域中,将包含一组可以建模的实际对象以及可以应用于它们的操作,在某些情况下,这些操作将是对象的一部分,但在某些其他情况下它们不会。 In particular you will find utility classes in the languages that claim that everything is an object and those utility classes are nothing but a layer trying to hide the fact that those methods don't belong to any particular object. 特别是你会发现语言中的实用程序类声称一切都是一个对象,而那些实用程序类只不过是一个试图掩盖这些方法不属于任何特定对象的事实的层。

Even some operations that are implemented as member functions are not really operations on the object. 甚至一些作为成员函数实现的操作也不是对象的真正操作。 Consider addition for a Complex number class, how is sum (or + ) more of an operation on the first argument than the second? 考虑添加Complex类,第一个参数的操作的sum (或+ )如何比第二个更多? Why a.sum(b); 为什么选择a.sum(b); or b.sum(a) , should it not be sum( a, b ) ? b.sum(a) ,如果不是sum( a, b )

Forcing the operations to be member methods actually produces weird effects --but we are just used to them: a.equals(b); 强制操作成为成员方法实际上会产生奇怪的效果 - 但我们只是习惯它们: a.equals(b); and b.equals(a); b.equals(a); might have completely different results even if the implementation of equals is fully symmetric. 即使equals的实现是完全对称的,也可能有完全不同的结果。 (Consider what happens when either a or b is a null pointer) (考虑当ab是空指针时会发生什么)

Boost Range Library's approach use operator|(). Boost Range Library的方法使用operator |()。

r | filtered(p);

I can write trim for string as follows in the same way, too. 我也可以用同样的方式为字符串编写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;
}

The short answer is that you cannot do that. 简短的回答是你不能这样做。 The long answer is that you can simulate it, but be aware that you'll have to create a lot of code as workaround (actually, I don't think there is an elegant solution). 答案很长,你可以模拟它,但要注意你必须创建大量的代码作为解决方法(实际上,我认为没有一个优雅的解决方案)。

In the discussion, a very complex workaround is provided using operator- (which is a bad idea, in my opinion). 在讨论中,使用operator-提供了一个非常复杂的解决方法(在我看来这是个坏主意)。 I guess that the solution provided in the dead link was more o less similar (since it was based on operator|). 我想死链接中提供的解决方案更不相似(因为它基于运算符|)。

This is based in the capability of being able to do more or less the same thing as an extension method with operators. 这基于能够与运算符的扩展方法做或多或少相同的能力。 For example, if you want to overload the ostream's operator<< for your new class Foo, you could do: 例如,如果你想为你的新类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
}

As I said, this is the only similar mechanism availabe in C++ for extension methods. 正如我所说,这是C ++中唯一可用于扩展方法的类似机制。 If you can naturally translate your function to an overloaded operator, then it is fine. 如果您可以自然地将您的函数转换为重载运算符,那么它很好。 The only other possibility is to artificially overload an operator that has nothing to do with your objective, but this is going to make you write very confusing code. 唯一的另一种可能性是人为地超载与您的目标无关的运算符,但这会让您编写非常混乱的代码。

The most similar approach I can think of would mean to create an extension class and create your new methods there. 我能想到的最相似的方法是创建一个扩展类并在那里创建新方法。 Unfortunately, this means that you'll need to "adapt" your objects: 不幸的是,这意味着你需要“适应”你的对象:

class stringext {
public:
    stringext(std::string &s) : str( &s )
        {}
    string trim()
        {  ...; return *str; }
private:
    string * str;
};

And then, when you want to do that things: 然后,当你想做那些事情时:

void fie(string &str)
{
    // ...
    cout << stringext( str ).trim() << endl;
}

As said, this is not perfect, and I don't think that kind of perfect solution exists. 如上所述,这并不完美,我认为不存在那种完美的解决方案。 Sorry. 抱歉。

This is the closest thing that I have ever seen to extension methods in C++. 这是我在C ++中看到的最接近扩展方法的东西。 Personally i like the way it can be used, and possibly this it the closest we can get to extension methods in this language. 我个人喜欢它的使用方式,也许这就是我们最接近这种语言的扩展方法。 But there are some disadvantages: 但是有一些缺点:

  • It may be complicated to implement 实施起来可能很复杂
  • Operator precedence may be not that nice some times, this may cause surprises 运算符优先级有时可能不那么好,这可能会导致意外

A solution: 一个办法:

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

This is not my personal invention, recently a friend of mine mailed it to me, he said he got it from a university mailing list. 这不是我的个人发明,最近我的一个朋友把它寄给了我,他说他是从大学邮件列表中得到的。

To elaborate more on @Akira answer, operator| 详细说明@Akira答案, operator| can be used to extend existing classes with functions that take parameters too. 可以用于扩展具有参数的函数的现有类。 Here an example that I'm using to extend Xerces XML library with find functionalities that can be easily concatenated: 这里是一个我用来扩展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

It can be used this way: 它可以这样使用:

auto packetRate = *elementRoot | FindFirst("Header") | FindFirst("PacketRate");
auto &decrypted = *elementRoot | FindFirstExisting("Header") | FindFirstExisting("Decrypted");

You can enable kinda extension methods for your own class/struct or for some specific type in some scope. 您可以为您自己的类/结构或某些范围内的某些特定类型启用kinda扩展方法。 See rough solution below. 见下面的粗略解决方案

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

Then inherit your class from this and use like 然后继承你的类并使用like

class SomeExtensible : public Extensible { /*...*/ };
std::function<int(SomeExtensible&, int)> fn;
SomeExtensible se;
int i = (se | fn)(4);

Or you can declare this operator in cpp file or namespace. 或者您可以在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)();

Or even wrap it in macro (I know, it's generally bad idea, nevertheless you can): 甚至包装在宏中(我知道,这通常是个坏主意,但你可以):

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

This syntactic sugar isn't available in C++, but you can define your own namespace and write pure static classes, using const references as the first parameter.这种语法糖在 C++ 中不可用,但您可以定义自己的命名空间并编写纯 static 类,使用const引用作为第一个参数。

For example, I was struggling using the STL implementation for some array operations, and I didn't like the syntaxis, I was used to JavaScript's functional way of how array methods worked.例如,我正在努力使用 STL 实现一些数组操作,我不喜欢语法,我已经习惯了 JavaScript 的函数式数组方法工作方式。

So, I made my own namespace wh with the class vector in it, since that's the class I was expecting to use these methods, and this is the result:因此,我使用其中的 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

I wouldn't inherit nor compose a class with it, since I've never been able to do it peacefully without any side-effects, but I came up with this, just const references.我不会继承它,也不会用它组合 class,因为我从来没有能够在没有任何副作用的情况下和平地做到这一点,但我想出了这个,只是const引用。

The problem of course, is the extremely verbose code you have to make in order to use these static methods:当然,问题是为了使用这些 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;
}

If only there was syntactic sugar that could make my static methods have some kind of more common syntax like:如果只有语法糖可以让我的 static 方法具有某种更常见的语法,例如:

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

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

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