2013-11-09 68 views
0

我在代碼中遇到了一個非常奇怪的錯誤,並設法將它降低到以下單一功能。爲什麼使用union,class和lambda會產生分段錯誤?

#include "either.hpp" 

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

int main() 
{ 
    using std::string; 
    using std::vector; 

    auto callback = []() 
    { 
     auto either = data::either<string,vector<int>>(string("test")); 
     return either; 
     return data::either<string,vector<int>>(string("test")); 
    }; 
    callback(); 
} 

運行時,該程序在std::string的析構函數上生成了段錯誤。 但是,如果我們刪除第二個return聲明,它完美的工作。

現在,data::either類正在使用具有兩個成員的聯合,並且lambda的return語句將導致對stack-allcated變量的銷燬。

使用這些功能是否存在問題,是因爲我不知道的原因導致的未定義行爲,還是僅僅是編譯器錯誤?

這裏是either類:

#ifndef EITHER_HPP 
#define EITHER_HPP 

#include <cassert> 
#include <functional> 
#include <type_traits> 
#include <utility> 

// Model the haskel Data.Either data type. 
// http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Either.html 

namespace data 
{ 
    template<class Left, class Right> 
    class either 
    { 
     static_assert(
      !std::is_same< 
       typename std::remove_cv<Left>::type, 
       typename std::remove_cv<Right>::type 
      >::value, 
      "data::either<A,B>: type A and B must be different."); 

     bool m_is_right; 
     union 
     { 
      Left m_left; 
      Right m_right; 
     }; 


    public: 
     explicit either(Left l) 
      : m_is_right(false) 
      , m_left(std::move(l)) 
     { 
     } 

     explicit either(Right r) 
      : m_is_right(true) 
      , m_right(std::move(r)) 
     { 
     } 

     either(either const& other) 
      : m_is_right(other.is_right()) 
     { 
      if (other.is_left()) 
      { 
       m_left = other.left(); 
      } 
      else 
      { 
       m_right = other.right(); 
      } 
     } 

     either(either&& other) 
      : m_is_right(other.is_right()) 
     { 
      if (other.is_left()) 
      { 
       m_left = std::move(other.left()); 
      } 
      else 
      { 
       m_right = std::move(other.right()); 
      } 
     } 

     ~either() 
     { 
      if (is_right()) 
      { 
       right().~Right(); 
      } 
      else 
      { 
       left().~Left(); 
      } 
     } 

     either& operator=(either const& other) 
     { 
      m_is_right = other.is_right(); 

      if (other.is_left()) 
      { 
       m_left = other.left(); 
      } 
      else 
      { 
       m_right = other.right(); 
      } 

      return *this; 
     } 

     either& operator=(either&& other) 
     { 
      m_is_right = other.is_right(); 

      if (other.is_left()) 
      { 
       m_left = std::move(other.left()); 
      } 
      else 
      { 
       m_right = std::move(other.right()); 
      } 

      return *this; 
     } 

     bool is_left() const 
     { 
      return !is_right(); 
     } 

     bool is_right() const 
     { 
      return m_is_right; 
     } 

     Left& left() 
     { 
      assert(is_left()); 
      return m_left; 
     } 

     Left const& left() const 
     { 
      assert(is_left()); 
      return m_left; 
     } 

     Right& right() 
     { 
      assert(is_right()); 
      return m_right; 
     } 

     Right const& right() const 
     { 
      assert(is_right()); 
      return m_right; 
     } 
    }; 
} 

#endif 

我用下面的編譯器進行了測試:

$ clang++ -std=c++11 test.cpp && ./a.out 
Segmentation fault 
$ clang++ --version 
Debian clang version 3.2-11 (tags/RELEASE_32/final) (based on LLVM 3.2) 
Target: x86_64-pc-linux-gnu 
Thread model: posix 

$ g++ -std=c++11 test.cpp && ./a.out 
Segmentation fault 
$ g++ --version 
g++ (Debian 4.7.3-4) 4.7.3 
Copyright (C) 2012 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+1

@jxh:從2011版C++版本開始,union可以使用構造函數存儲類型。但是,有必要手動進行正確的初始化,銷燬和分配。在C++ 03中,你只能在一個'union'中使用POD。 –

+0

@DietmarKühl我認爲[class.dtor]/5甚至說應該刪除工會的職權,因爲'string'(和'vector')的dtor並不是微不足道的。 – dyp

+0

@DyP:約定:如果存在一個不重要的成員存儲在'union'中,析構函數會被默認刪除。當然,你可以提供一個用戶定義的析構函數來做正確的事情(例如,因爲封閉類使用合適的描述符進行適當的銷燬,並且'union'是'private'),所以什麼也不做。 –

回答

2

似乎在拷貝構造函數,你認爲m_leftm_right適合的方式進行初始化分配std::vector<int>std::string。然而,該成員很可能根本沒有初始化爲適合該類:據我所知,它是零初始化的。我沒有使用C++ 11級的工會不夠好,但我認爲你需要使用

new(&this->m_left) Left(other.m_left); 

,同樣也m_right。顯然,在移動構造器中,還需要插入std::move()

請注意,您的副本分配也可能存在嚴重缺陷:它假定分配的左側與右側具有相同的類型。如果存儲的類型不同,則分配而不是工作!如果存儲的類型不同,則需要銷燬左側的內容並在其中構建右側的副本。副手,我想我會使用副本&交換方法利用現有的副本構造函數和析構函數來創建一個強大的異常安全分配(如果交換相應的當前元素是非拋出)。

+0

我認爲[class.base.init]/8表示對匿名聯合數據成員執行* no *初始化。但是,似乎可以在周圍類的mem-initializer-list中指定一個匿名聯合的成員。 – dyp

+0

好吧,我明白爲什麼它會失敗,我該怎麼做,但我仍然不明白爲什麼如果只有第一個'return'語句不會出現問題。它看起來應該總是失敗... – authchir

相關問題