2013-04-04 31 views
3

這裏是一個simplified example爲什麼不能shared_ptr解析函數接口中的繼承關係?

#include <memory> 
#include <vector> 

template< class T > 
class K 
{ 
public: 
    virtual ~K(){} 
}; 

class KBOUM : public K<int>{}; 

template< class U > 
void do_something(std::shared_ptr< K<U> > k) { } 

int main() 
{ 
    auto kboom = std::make_shared<KBOUM>(); 
    do_something(kboom); // 1 : error 

    std::shared_ptr< K<int> > k = kboom; // 2 : ok 
    do_something(k); // 3 : ok 

} 

帶或不帶助力,無論我使用編譯器我得到#錯誤1,因爲shared_ptr<KBOOM>不從shared_ptr<K<int>>繼承。 但是,KBOOM確實繼承自K<int>。你可以看到#2工作,因爲shared_ptr被設計爲允許隱式地將子類指針傳遞給基類指針,比如原始指針。

所以我的問題是:

  1. 什麼預防的std :: shared_ptr的實施者,使其在情況#1的工作(我的意思是,假設標準並防止這種情況下,應該有一個理由);
  2. 有沒有一種方法可以寫出auto kboom = std::make_shared<KBOUM>(); do_something(kboom);而不從KBOOM繼承的K中查看int類型?

注:我想避免功能的用戶有寫

std::shared_ptr<K<int>> k = std::make_shared<KBOOM>(); 

do_something(std::shared_ptr<K<int>>(kboom)); 
+0

爲什麼要讓'KBOUM'成爲派生類型而不是typedef? – ildjarn 2013-04-13 00:09:39

+0

@ildjarn因爲它是一個簡化的例子,正如第一行所指出的那樣。在這個例子是簡化的真實代碼中,KBOUM不是空的,並且包含與K一起使用的參數的特定代碼。從K繼承的每種類型都會做一些不同的事情。 – Klaim 2013-04-13 20:25:27

回答

6

這是不相關的std::shared_ptr<>。事實上,你可以替換成任何類模板,並得到同樣的結果:

template<typename T> struct X { }; 

class KBOUM : public X<int> { }; 

template<typename U> 
void do_something(X<K<U>> k) { } 

int main() 
{ 
    X<KBOUM> kboom; 
    do_something(kboom); // ERROR! 

    X<K<int>> k; 
    do_something(k); // OK 
} 

這裏的問題是,類型參數推導正試圖找到一個絕配,並衍生到基地轉換不是企圖。

只有之後所有模板參數已被明確推斷爲產生完美匹配(標準允許的少數例外情況),在重載解析期間可能會考慮參數之間的轉換。

解決方法:

它可以計算出基於通過KerrekSBthis Q&A on StackOverflow發佈的解決方案一種解決方法。首先,我們應該定義一個類型特徵,使我們能夠告訴是否某一類是從某個模板的實例得出:

#include <type_traits> 

template <typename T, template <typename> class Tmpl> 
struct is_derived 
{ 
    typedef char yes[1]; 
    typedef char no[2]; 

    static no & test(...); 

    template <typename U> 
    static yes & test(Tmpl<U> const &); 

    static bool const value = sizeof(test(std::declval<T>())) == sizeof(yes); 
}; 

然後,我們可以使用SFINAE改寫do_something()如下(注意: C++ 11允許功能模板參數默認參數):

template<class T, std::enable_if<is_derived<T, K>::value>* = nullptr> 
void do_something(X<T> k) 
{ 
    // ... 
} 

隨着這些改變,該程序將正確編譯:

int main() 
{ 
    X<KBOUM> kboom; 
    do_something(kboom); // OK 

    X<K<int>> k; 
    do_something(k); // OK 
} 

這是一個live example

+0

啊,是的,我記得以前遇到過這個問題。有沒有辦法解決? – Klaim 2013-04-04 15:17:48

+0

@Klaim:恩,實際上,我在之前的評論(我刪除)中給你的建議是不正確或不完整的。讓我想想一個解決方法 – 2013-04-04 15:22:48

+0

@Klaim:我更新了答案,包括一個解決方法 – 2013-04-04 15:46:14

1

Andy Prowl對這個問題給出了一個完美的解釋,並提出了一個聰明的解決方法。我想通過一些努力,解決方法也可以適用於C++ 03。 (我沒有嘗試過,這只是猜測而已)。

我只想提出一個簡單的解決方法,它只能用於C++ 11。所有你需要做的就是創建這個過載:

template< class T > 
auto do_something(const std::shared_ptr<T>& k) -> 
    decltype(do_something(std::shared_ptr< K<int> >(k))) 
{ 
    return do_something(std::shared_ptr< K<int> >(k)); 
} 

基本上,它探測如果do_something(std::shared_ptr< K<int> >(k))是法律通過decltype(和SFINAE)。如果是這樣,那麼這個重載將執行「強制轉換爲基礎」並將調用委託給基類爲shared_ptr的重載。

更新:

更一般地,如果你有一個函數,說do_something接受一個shared_ptr<Base>和你想要的編譯器調用它,當你通過一個shared_ptr<T>其中T是任何類型的公開從Base派生那麼解決方法是這樣的:

class Base {}; 
class Derived : public Base {}; 

// The original function that takes a std::shared_ptr<Base> 
void do_something(const std::shared_ptr<Base>&) { 
    // ... 
} 

// The workaround to take a shared_ptr<T> where T publicly derives from Base 
template <typename T> 
auto do_something(const std::shared_ptr<T>& pd) -> 
    decltype(do_something(std::shared_ptr<Base>(pd))) { 
    return do_something(std::shared_ptr<Base>(pd)); 
} 

// Example: 
int main() { 
    auto pd = std::make_shared<Derived>(); 
    do_something(pd); 
} 
+0

非常好!會嘗試! – Klaim 2013-04-04 17:29:05

0
#include <memory> 
#include <utility> 

template< template<typename T>class Factory, typename T > 
struct invert_factory {}; 

template< template<typename T>class Factory, typename U > 
struct invert_factory< Factory, Factory<U> > { 
    typedef U type; 
}; 
template<typename T> 
struct K {}; 

template<template<typename>class Factory, typename Default, typename U> 
U invert_implicit_function(Factory<U> const&); 
template<template<typename>class Factory, typename Default> 
Default invert_implicit_function(...); 

template<template<typename>class Factory, typename U> 
struct invert_implicit { 
private: 
    struct unused_type{}; 
public: 
    typedef decltype(invert_implicit_function<Factory, unused_type>(std::declval<U>())) type; 
    enum{ value = !std::is_same< unused_type, type >::value }; 
}; 

template<typename spKU, typename=void > 
struct is_shared_ptr_to_KU {}; 

template<typename spKU> 
struct is_shared_ptr_to_KU< spKU, 
    typename std::enable_if< 
    invert_implicit< K, 
      typename invert_factory<std::shared_ptr, spKU>::type 
    >::value 
    >::type 
>:std::true_type {}; 

template< typename spKU > 
auto do_something(spKU)->typename std::enable_if< is_shared_ptr_to_KU<spKU>::value >::type { } 

struct Blah:K<int> {}; 
int main() { 
    static_assert(invert_implicit< K, K<int> >::value, "one"); 
    static_assert(invert_implicit< K, Blah >::value, "two"); 
    do_something(std::shared_ptr<K<int>>()); 
    do_something(std::shared_ptr<Blah>()); 
// do_something(0); 
// do_something(std::shared_ptr<int>()); 
} 

需要一點潤色,但確實你是什麼樣的國王。

處理shared_ptr<K<U>>採取了額外的間接水平。

還包括如果需要實際提取U類型的方法。 (如果invert_implicit::value爲真,則invert_implicit::typeU)。

請注意,可以隱式轉換爲K<U>的類可以符合條件 - 如果您願意,也可以檢查is_derived