簡體   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