c++ - 从基类访问 union 的公共(public)部分

标签 c++ c++11 strict-aliasing

我有一个 Result<T>包含一些 union 的模板类 error_typeT .我想在不求助于虚函数的情况下公开基类中的公共(public)部分(错误)。

这是我的尝试:

using error_type = std::exception_ptr;

struct ResultBase
{
    error_type error() const
    {
        return *reinterpret_cast<const error_type*>(this);
    }

protected:
    ResultBase() { }
};

template <class T>
struct Result : ResultBase
{
    Result() { new (&mError) error_type(); }

    ~Result() { mError.~error_type(); }

    void setError(error_type error) { mError = error; }

private:
    union { error_type mError; T mValue; };
};

static_assert(std::is_standard_layout<Result<int>>::value, "");

void check(bool condition) { if (!condition) std::terminate(); }

void f(const ResultBase& alias, Result<int>& r)
{
    r.setError(std::make_exception_ptr(std::runtime_error("!")));
    check(alias.error() != nullptr);

    r.setError(std::exception_ptr());
    check(alias.error() == nullptr);
}

int main()
{
    Result<int> r;
    f(r, r);
}

(这是精简的,如果不清楚,请参阅 extended version)。

基类利用标准布局在偏移量零处找到错误字段的地址。然后它将指针转换到 error_type (假设这确实是 union 的当前动态类型)。

我认为这是可移植的是否正确?或者它是否违反了一些指针别名规则?


编辑:我的问题是“这是可移植的吗”,但许多评论者对这里使用继承感到困惑,所以我会澄清一下。

首先,这是一个玩具示例。请不要从字面上理解它或假设基类没有用。

设计有三个目标:

  1. 紧凑度。错误和结果是互斥的,所以它们应该在 union 中。
  2. 没有运行时开销。虚函数被排除在外(另外,持有虚表指针与目标 1 冲突)。 RTTI 也被排除在外。
  3. 一致性。不同的公共(public)字段Result类型应该可以通过同质指针或包装器访问。例如:如果不是 Result<T>我们在谈论 Future<T> , 应该可以做到 whenAny(FutureBase& a, FutureBase& b)不管a/b具体类型。

如果愿意牺牲 (1),这就变得微不足道了。像这样的东西:

struct ResultBase
{
    error_type mError;
};

template <class T>
struct Result : ResultBase
{
    std::aligned_storage_t<sizeof(T), alignof(T)> mValue;
};

如果我们牺牲 (2) 而不是目标 (1),它可能看起来像这样:

struct ResultBase
{
    virtual error_type error() const = 0;
};

template <class T>
struct Result : ResultBase
{
    error_type error() const override { ... }

    union { error_type mError; T mValue; };
};

同样,理由不相关。我只想确保原始示例符合 C++11 代码。

最佳答案

回答问题: 那是可移植的吗?

不可能


详细信息:

如果没有至少 type erasure,这是不可能的(不需要 RTTI/dynamic_cast,但至少需要一个虚函数)。已经有类型删除的工作解决方案 ( Boost.Any )

原因如下:

  • 你想实例化这个类

    Result<int> r;

实例化模板类意味着允许编译器推断成员变量的大小,以便它可以在堆栈上分配对象。

但是在您的实现中:

private:
union { error_type mError; T mValue; };

你有一个变量 error_type您似乎想以多态方式使用它。但是,如果您在模板实例化时修复了类型,您以后将无法更改它(不同的类型可能具有不同的大小!您也可以强加自己来修复对象的大小,但不要那样做。丑陋和骇人听闻)。

所以你有 2 个解决方案,使用虚函数,或者使用错误代码。

有可能做你想做的事,但你不能那样做:

 Result<int> r;
 r.setError(...);

具有您想要的确切界面。

很多可能的解决方案,只要你允许虚函数和错误代码,为什么你不想在这里使用虚函数?如果性能很重要,请记住“设置”错误的成本与设置指向虚拟类的指针一样多(如果没有错误,则不需要解析 Vtable,无论如何模板代码中的 Vtable 是大多数时候可能会被优化掉)。

此外,如果您不想“分配”错误代码,您可以预先分配它们。

您可以执行以下操作:

template< typename Rtype>
class Result{
     //... your detail here


    ~Result(){
         if(error)
             delete resultOrError.errorInstance;
         else
             delete resultOrError.resultValue;
    }

private:
    union {
        bool error;
        std::max_align_t mAligner;
    };
    union uif 
    { 
        Rtype               *          resultValue;
        PointerToVirtualErrorHandler  errorInstance;
    } resultOrError;
}

您有 1 个结果类型,或 1 个指向具有所需错误的虚拟类的指针。您检查 bool 值以查看当前是否有错误或结果,然后您从 union 中获得相应的值。虚拟成本仅在出现错误时才支付,而对于常规结果,您只需支付 bool 检查的罚金。

当然,在上面的解决方案中,我使用了一个指向结果的指针,因为它允许通用结果,如果您对基本数据类型结果或仅具有基本数据类型的 POD 结构感兴趣,那么您也可以避免对结果使用指针。

注意在你的情况下std::exception_ptr 确实已经进行了类型删除,但是您丢失了一些类型信息,要再次获取丢失的类型信息,您可以自己实现类似于 std::exception_ptr 的东西但有足够的虚方法来允许安全地转换为正确的异常类型。

关于c++ - 从基类访问 union 的公共(public)部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33069102/

相关文章:

c++ - 如何将 GCC 的 printf 格式属性与 C++11 可变参数模板一起使用?

C - 用于算术的不兼容指针是否违反严格别名?

c - 使用指针访问 long long 变量中的字节

c++ - 这个回文划分算法的时间复杂度是多少?

c++ - 没有互斥锁的事件通知

c++ - 使用字符串 vector 初始值设定项时,出现运行时错误 "terminate called after throwing instance of ` std::length_error`"

c++ - 模板和继承打破了严格的别名规则

c++ - 当我使用的库使用的库发生变化时,我的可执行文件是否需要重新链接?

c++ - 不完整类型(类)的使用无效

c++ - 在 OpenGL 中哪种渲染对象的方式更有效?