2017-02-15 108 views
3

我正在處理一個包含多個包的項目。在我的基本包一個,我宣佈一個智能指針,這樣的(這裏是完整的代碼):德爾福 - 智能指針構造函數的奇怪行爲

unit UTWSmartPointer; 

interface 

type 
    IWSmartPointer<T> = reference to function: T; 

    TWSmartPointer<T: class, constructor> = class(TInterfacedObject, IWSmartPointer<T>) 
    private 
     m_pInstance: T; 

    public 
     constructor Create; overload; virtual; 

     constructor Create(pInstance: T); overload; virtual; 

     destructor Destroy; override; 

     function Invoke: T; virtual; 
    end; 

implementation 
//--------------------------------------------------------------------------- 
constructor TWSmartPointer<T>.Create; 
begin 
    inherited Create; 

    m_pInstance := T.Create; 
end; 
//--------------------------------------------------------------------------- 
constructor TWSmartPointer<T>.Create(pInstance: T); 
begin 
    inherited Create; 

    m_pInstance := pInstance; 
end; 
//--------------------------------------------------------------------------- 
destructor TWSmartPointer<T>.Destroy; 
begin 
    m_pInstance.Free; 
    m_pInstance := nil; 

    inherited Destroy; 
end; 
//--------------------------------------------------------------------------- 
function TWSmartPointer<T>.Invoke: T; 
begin 
    Result := m_pInstance; 
end; 
//--------------------------------------------------------------------------- 

end. 

在我的項目後來(在另包),我用這個智能指針與GDI +對象(一個TGpGraphicsPath)。我聲明瞭這樣的圖形路徑:

... 
pGraphicsPath: IWSmartPointer<TGpGraphicsPath>; 
... 
pGraphicsPath := TWSmartPointer<TGpGraphicsPath>.Create(); 
... 

但是,當我執行代碼時,屏幕上沒有畫任何東西。我沒有得到任何錯誤,沒有例外或訪問違規,只是一個空白頁面。但是,如果我只是改變我的代碼這樣的:

... 
pGraphicsPath: IWSmartPointer<TGpGraphicsPath>; 
... 
pGraphicsPath := TWSmartPointer<TGpGraphicsPath>.Create(TGpGraphicsPath.Create); 
... 

然後都成爲精品,和我的道路究竟是畫如預期。但我不明白爲什麼第一個構造函數不能按預期工作。有人可以向我解釋這種奇怪的行爲?

Regards

+3

我的猜測是,TGpGraphicsPath有一個可選的參數,在這種情況下TObject.Create將被調用,而不是TGpGraphicsPath.Create(optionalArgument) - 你必須注意的泛型。 – Dsm

+1

@Remy事實證明,匿名方法實際上是接口,並且實現細節以這種方式被廣泛使用:http://stackoverflow.com/a/39955320 –

+0

@Dsm TGpGraphicsPath'在其構造函數中沒有參數。 –

回答

4

這是一個相當複雜的陷阱,你陷入了困境。當你寫:

TGpGraphicsPath.Create 

你可能認爲你正在調用無參數的構造函數。但事實並非如此。你實際上調用這個構造函數:

constructor Create(fillMode: TFillMode = FillModeAlternate); reintroduce; overload;  

你不提供參數,所以默認值是由編譯器提供的。

在你的智能指針類你寫:

T.Create 

這真的是調用參數的構造函數。但這是由TObject定義的構造函數。當使用該構造函數時,TGPGraphicsPath實例未正確初始化。

如果打算使用constructor通用約束,還必須確保始終使用可以使用無參數構造函數正確構造的類。對你而言不幸的是TGPGraphicsPath不符合法案。事實上,這類課程佔優勢。

真的沒有很多,你可以在這裏做,以避免顯式調用構造函數。對於這個特定的類,你的智能指針類幾乎不可能確定調用哪個構造函數。

我的建議是擺脫constructor通用約束,並強制智能指針類的使用者顯式實例化該實例。

這是一個相當普遍的問題 - 我不到一個星期前在這裏回答了類似的問題:Why does a deserialized TDictionary not work correctly?

+0

由泛型/約束的原始開發人員完成的一個非常令人討厭的錯誤,並將它們從C#中自動繼承的C#中複製出來。使用ctor約束有意義,並且實際上可以防止使用沒有聲明無參數的類,儘管它們的祖先可能有一個類。在Delphi中,一旦子類有過載的ctors,編譯器就會看到TObject ctor。但現在我寫了這個,我認爲這可以通過編譯器在這裏進行適當的重載解析來解決,因爲在這種情況下它不應該看到TObject ctor。 –

+0

@stefan我不認爲編譯器可以將默認的參數構造函數作爲無參數的構造函數,除非底層設計被徹底改變。 –

+0

我不明白爲什麼編譯器不能簡單地查看類型中的可用構造函數,並且如果找到無參數構造函數,則查找具有所有默認參數的構造函數。用戶代碼無論如何都是相同的,並且這通常在泛型之外正常工作,所以無論代碼是否在泛型中,編譯器都應該做正確的事情。如果它的定義被放寬了一點以包含任何不需要*用戶代碼的輸入參數的構造函數,那麼這個改變不應該破壞'構造函數'約束。 –