2017-07-11 18 views
6

Tôi đã dành vài ngày qua để cố gắng tạo ra một trình bao bọc chung cho các con trỏ hàm trong C++ và tôi đã quản lý để giải quyết gần như mọi thanh vấn đề đơn lẻ. Mục tiêu chính của tôi với điều này là để có thể chỉ đơn giản gọi một đối tượng như một hàm với một con trỏ hàm được lưu trữ nội bộ. Nếu con trỏ trỏ đến đâu đó thì nó sẽ gọi nó như bình thường trong khi một con trỏ null sẽ không gọi hàm đó và nó sẽ tiếp tục như thể không có gì xảy ra. Tôi dự định sử dụng chủ yếu cho các mục đích chức năng gọi lại, nơi mà tôi có thể sẽ không quan tâm nếu hàm đó được gọi hay không và chỉ muốn tạo thành một hành động. Nó hoạt động gần như hoàn hảo với những điều sau:Sử dụng gói tham số '...' của C làm giá trị mẫu C++?

template<typename T> 
class Action; 

template<typename TReturn, typename ... TArgs> 
class Action<TReturn(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef TReturn(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TReturn& pReturn, TArgs ... pArgs) const { if (!mPointer) return false; pReturn = mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

template<typename ... TArgs> 
class Action<void(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef void(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TArgs ... pArgs) const { if (!mPointer) return false; mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

Tuy nhiên, tôi cảm thấy tình huống có khả năng sử dụng trình bao bọc này là đầu ra của thông tin gỡ lỗi hoặc văn bản được định dạng. Điều này có thể thông qua các hàm do người dùng định nghĩa hoặc các hàm sẵn có như printf. Để khớp với chữ ký của printf, Hành động sẽ được tạo như:

Action<int(const char*, ...)> callback = printf; 

và nó có thể hoạt động giống như cách hành động khác. Vấn đề tôi đang tìm kiếm là '...' sẽ buộc chữ ký mẫu không phù hợp với một trong hai chuyên môn, thay vào đó là chữ ký đầu tiên chỉ là mẫu thử nghiệm.

Tôi hoàn toàn có thể hiểu tại sao điều này không hiệu quả và tại sao trình biên dịch không thể xử lý việc tạo lớp được yêu cầu nhưng tôi hy vọng rằng ai đó ở đây sẽ biết bất kỳ cách lén lút nào để đạt được điều này hoặc tương tự. Bất kỳ trợ giúp sẽ được nhiều đánh giá cao, nhờ :)

+7

Tại sao không sử dụng 'std :: function'? – StoryTeller

+0

Mục đích của 'Hành động' là gì? Bạn có yêu cầu gì về việc bạn không thể sử dụng ['std :: function'] (http://en.cppreference.com/w/cpp/utility/functional/function)? 'Hành động' giải quyết vấn đề gì' std :: function' không có vấn đề gì? –

+1

https://stackoverflow.com/questions/18370396/why-cant-stdfunction-bind-to-c-style-variadic-functions đưa ra một số bình luận, về cách đạt được gần như thế này. Tôi nghĩ rằng đối với mỗi hàm variadic, bạn sẽ cần một lớp cụ thể để giúp bạn, và hy vọng có một biến thể v * của hàm bạn có thể gọi. – mksteve

Trả lời

1

Ví dụ sau đây làm việc cho tất cả các loại chức năng, và lambdas không có chụp:

#include <utility> 
#include <cstdio> 
#include <cmath> 

template<typename Fn> 
class Action { 
    Fn* function_ptr;   
public: 
    Action() noexcept : function_ptr(nullptr) {}  
    Action(std::nullptr_t) noexcept : function_ptr(nullptr) {}  
    Action(const Action& other) : function_ptr(other.function_ptr) {}  
    Action(Fn f) : function_ptr(f) {} 

    Action& operator=(const Action& other) { 
     return (function_ptr = other.function_ptr, *this); 
    }    
    Action& operator=(std::nullptr_t) { 
     return (function_ptr = nullptr, *this); 
    }  
    Action& operator=(Fn f) { 
     return (function_ptr = f, *this); 
    } 

    template<typename... Params> 
    auto operator()(Params&&... params) { 
     return function_ptr(std::forward<Params>(params)...); 
    } 
}; 

Live Demo

Theo các ý kiến ​​đề cập đến dưới câu hỏi của bạn , sử dụng std::function tốt hơn viết một trình bao bọc cho các con trỏ hàm. Vấn đề duy nhất với std::function là nó không thể được sử dụng với các chữ ký chức năng có chứa dấu ba chấm. Nếu bạn chỉ định rõ ràng chữ ký, bạn có thể lưu trữ ví dụ: printf như sau:

std::function<int(const char*, int, double, double)> fn = printf; 

Nếu bạn sử dụng C++ 17 hoặc Boost, bạn có thể thực hiện bạn sở hữu printf -like chức năng, sử dụng std::any hoặc Boost.Any có thể được giao cho std::function như sau:

#include <iostream> 
#include <string> 
#include <vector> 
#include <any> 
#include <functional> 

using namespace std; 

void any_printf(string&& format, vector<any>&& args) { 
    int arg_index = 0; enum { NORMAL, CONVERT } mode; 

    for(auto& chr : format) { 
     if(mode == CONVERT) { 
      switch(chr) { 
      case 'd': cout << any_cast<int>(args[arg_index++]); break; 
      case 'f': cout << any_cast<float>(args[arg_index++]); break; 
      case 's': cout << any_cast<string>(args[arg_index++]); break; 
      /* ... */ 
      default: cout << chr; 
      }; 
      mode = NORMAL; 
     } 
     else { 
      chr == '%' ? (mode = CONVERT, 0) : (cout << chr, 0); 
     } 
    } 
} 

int main() { 
    using namespace string_literals; 
    function<void(string&&, vector<any>&&)> f_ptr { any_printf }; 

    f_ptr("Cuboid: %smm x %dmm x %fmm.\n", { any("3"s), any(4), any(6.67f) }); 
    return 0; 
} 
+0

Đó là một cách thú vị để đi về việc làm cho nó hoạt động.Vấn đề duy nhất của tôi với nó là nó đòi hỏi một giá trị tồn tại để có được chữ ký cho mẫu. Nó không phải là dễ dàng cho việc giảm tốc độ đơn giản của callbacks chức năng mong muốn mà không có ít nhất một chức năng duy nhất trong tâm trí để phù hợp với tình hình. Tôi đã đoán chính xác loại cú pháp mà tôi đã theo sau sẽ gần nếu không phải là không thể. Cảm ơn bạn đã trả lời nhanh :) – MitchellCroft

+1

@MitchellCroft, khi bạn viết một hàm sẽ gọi hàm gọi lại, bạn phải xác định chữ ký của nó. Nếu bạn muốn ** gọi lại cuộc gọi đó với số lượng biến dữ liệu và với các kiểu biến ** (như 'printf' có thể được gọi), bạn có thể sử dụng ví dụ: một 'std :: vector' của [' std :: any'] (http://en.cppreference.com/w/cpp/utility/any) type. Đây là một tính năng C++ 17, nhưng [Boost.Any] (http://www.boost.org/doc/libs/1_64_0/doc/html/any.html) cũng có thể được sử dụng như một sự thay thế. – Akira

+0

@MitchellCroft, tôi đã chỉnh sửa câu trả lời của mình bằng cách tiếp cận 'std :: any' mà tôi đã đề cập trong phần bình luận trước. – Akira

Các vấn đề liên quan