2017-10-20 100 views
-3

爲了理解對象切片的問題,我認爲我創建了一個可怕的例子,我試圖測試它。但是,這個例子並沒有我想象的那麼糟糕。瞭解對象切片

下面是一個最簡單的工作示例,如果您能幫助我理解爲什麼它仍「正常工作」,我將不勝感激。如果你幫我讓這個例子變得更糟,那會更好。

#include <functional> 
#include <iostream> 

template <class T> class Base { 
protected: 
    std::function<T()> f; // inherited 

public: 
    Base() : f{[]() { return T{0}; }} {} // initialized 
    virtual T func1() const { return f(); } 
    virtual ~Base() = default; // avoid memory leak for children 
}; 

template <class T> class Child : public Base<T> { 
private: 
    T val; 

public: 
    Child() : Child(T{0}) {} 
    Child(const T &val) : Base<T>{}, val{val} { // initialize Base<T>::f 
    Base<T>::f = [&]() { return this->val; }; // copy assign Base<T>::f 
    } 
    T func1() const override { return T{2} * Base<T>::f(); } 
    void setval(const T &val) { this->val = val; } 
}; 

template <class T> T indirect(const Base<T> b) { return b.func1(); } 

int main(int argc, char *argv[]) { 
    Base<double> b; 
    Child<double> c{5}; 

    std::cout << "c.func1() (before): " << c.func1() << '\n'; // as expected 
    c.setval(10); 
    std::cout << "c.func1() (after): " << c.func1() << '\n'; // as expected 

    std::cout << "indirect(b): " << indirect(b) << '\n'; // as expected 
    std::cout << "indirect(c): " << indirect(c) << '\n'; // not as expected 

    return 0; 
} 

輸出,當我編譯代碼如下獲得:

c.func1() (before): 10 
c.func1() (after): 20 
indirect(b): 0 
indirect(c): 10 

我希望最後一行拋出一些異常或者乾脆失敗。當c的基本部分被切片爲indirect時,在lambda表達式中沒有this->val被使用(我知道,C++是靜態編譯語言,而不是動態的)。我也嘗試在複製分配Base<T>::f時通過值捕獲this->val,但它沒有改變結果。

基本上,我的問題是兩個褶皺。首先,這是未定義的行爲,還是僅僅是一個合法的代碼?其次,如果這是一個合法的代碼,爲什麼行爲不受切片的影響?我的意思是,我可以看到T func1() const是從Base<T>部分調用的,但爲什麼捕獲的值不會造成任何麻煩?

最後,我怎樣才能建立一個例子,使內存訪問類型的問題更糟糕的副作用?

預先感謝您的時間。

編輯。我知道已被標記爲重複的其他主題。我已經閱讀了所有帖子,其實我一直試圖在那裏複製the last post。正如我以上問,我試圖得到的行爲

然後關於成員酒吧b中的信息丟失在一個。

我不能完全得到。對我而言,只有部分信息似乎丟失了。基本上,在最後一篇文章中,該人聲稱

來自實例的額外信息已經丟失,並且f現在傾向於未定義的行爲。

在我的例子中,f似乎也工作得很好。相反,我只是打電話給T Base<T>::func1() const,這並不奇怪。

+0

「_and f是現在容易未定義behaviour_」「我_IN例如,'F'似乎well._」未定義行爲是不確定的是剛剛工作。未定義行爲的許多可能表現之一是代碼_seeming_正常工作。 –

+0

@AlgirdasPreidžius,上面的行爲也是如此,在我的例子中,undefined?我是否僅僅因爲在這個有限的,最簡單的例子中運氣或緩存了一些變量而簡單地獲取_seemingly_工作代碼?這就是我想要了解的。 –

+0

我不知道,如果你的例子中的代碼產生未定義的行爲。我只是想說明,代碼_seems_正常工作的事實不是**證明它不會顯示未定義的行爲。 –

回答

1

當前的代碼沒有未定義的行爲。但是,這很危險,因此很容易造成未定義的行爲。

切片發生,但您訪問this->val。看起來像魔術,但你只是從你的主要訪問Child<double> cthis->val

這是因爲lambda捕獲。您捕獲this,它指向您的主變量中的c變量。然後,您將該lambda分配到您的基類中的std::function。您的基類現在有一個指向c變量的指針,以及通過std::function訪問val的一種方法。

因此發生切片,但您可以訪問未切割的對象。

這也是爲什麼數字不乘以二。虛擬呼叫解析爲基數,並且主體中c中的val的值爲10

你的代碼大致相當於:

struct B; 

struct A { 
    B* b = nullptr; 

    int func1() const; 
}; 

struct B : A { 
    int val; 
    explicit B(int v) : A{this}, val{v} {} 
}; 

int A::func1() const { 
    return b->val; 
} 

int main() { 
    B b{10}; 

    A a = b; 

    std::cout << a.func1() << std::endl; 
} 
+0

謝謝你們兩個人的回答,而不是簡單地聲稱這篇文章是重複的,在你的解釋中非常具有教育意義。現在我還可以看到這段代碼有多危險,特別是當'B'(或者''Child'')實例離開作用域並且'this'在'Child'的'Base'切片中失效時。這可能是https://stackoverflow.com/a/46725480/4720025中的行爲,我首先想明白這一點。你是否也可以詳細闡述一下按價值捕捉?我沒有想到我捕捉到了這個,但只是'this-> val'。 –

+0

@ArdaAytekin如果你有C++ 14,你可以像這樣捕獲:'val = this-> val'。它會按值捕獲'val'。請注意,如果「Child」中的值更改,lambda中的值將不會更新。 –

+0

爲什麼我需要C++ 14?我的意思是,不是通過C++ 11支持的值捕獲? C++ 11中的哪個特定功能支持這種類型的行爲? –