Small对象优化(又称SOO),这基本上是一个存储函数指针和类型擦除指针的联合。

问题描述 投票:0回答:1
reinterpret_cast

在构造函数中,如果我发现对象的大小小于联合,我将其直接存储在与位置新的位置并丢弃新位置的返回值。
template<typename Signature> class UniqueFunction; template<typename Ret, typename... Args> class UniqueFunction<Ret( Args... )> { struct AnyFn { virtual ~AnyFn() noexcept = default; virtual Ret operator()( Args... args ) = 0; }; template<typename Fn> struct FnContainer : public AnyFn { Fn fntor_; FnContainer( Fn fntor ) : fntor_ { std::move( fntor ) } {} FnContainer( const FnContainer& ) = delete; FnContainer& operator=( const FnContainer& ) & = delete; virtual ~FnContainer() noexcept = default; Ret operator()( Args... args ) override { return fntor_( std::forward<Args>( args )... ); } }; union { typename std::add_pointer<Ret( Args... )>::type fptr_; AnyFn* ftor_; } data_; // The union is here. // Tag that identifies the type of data currently stored in the union. enum class Tag : std::uint8_t { None, Fptr, FtorInline, FtorDync } tag_; // other methods... };

在这种情况下,我将重新解释联合的地址为template<typename F> UniqueFunction( F functor ) { if ( sizeof( FnContainer<F> <= sizeof data_ ) { new ( &data_ ) FnContainer<F>( std::move( functor ) ); tag_ = Tag::FtorInline; } else { data_.ftor_ = new FnContainer<F>( std::move( functor ) ); tag_ = Tag::FtorDync; } 通过

AnyFn*

尝试使用基本指针访问该区域上存储的派生对象。

reinterpret_cast

我知道在C ++标准中有一条规则称为严格的别名,因此我怀疑我的优化违反了标准并引起某种UB。不过,我不太确定,因为该代码在测试中效果很好。

在线测试是

。 问题是:
这是UB吗?如果是这样,我是否有另一种方法可以实现我想要的小物体优化?
    

放置新呼叫不是UB。但是它结束了联合对象的寿命,因为它重用了联合对象所占据的存储。请参阅
[basic.life]/2.5

if ( tag_ == Tag::FtorInline ) { // When I'm trying to deconstruct it, I do this: // reinterpret_cast<AnyFn*>( &data_ )->~AnyFn(); // Other cases, I do this: ( *reinterpret_cast<AnyFn*>( &data_ ) ); }

成为指向不在其寿命内的对象的指针。
&data_
不会产生指向

reinterpret_cast
对象的指针。它等同于a
AnyFn

static_cast

,然后是第二个void*

static_cast

。第一个演员很好。当该对象与原始Pointee(Dead Union对象)(死去的联合对象)

pointer-interconvertible

时,第二个仅将实际生成指向

AnyFn*
c++ c++11 optimization undefined-behavior unions
1个回答
0
投票
[expr.static.cast]/13

。这种情况将无法满足。通过结果指针访问将是UB。 有时可以通过将铸件的结果传递到

AnyFn

来获得指向

AnyFn

基类子对象的指针。但是,只有当
std::launder
基类子对象实际上位于
AnyFn
存储的开头时,这才能起作用。无法保证这种情况。
要实施小对象优化,建议将一个或
data_
的数组作为联盟成员之一。然后将小物体放置在该数组中。位置返回的指针新的新成员存储,用于访问创建的对象。
	
© www.soinside.com 2019 - 2025. All rights reserved.