【问题标题】:How to assign struct values in a template class function?如何在模板类函数中分配结构值?
【发布时间】:2018-10-16 10:36:08
【问题描述】:

我有一堆结构需要添加到我的向量中并给它们一些默认值。

  1. 结构是外部的,我无法更改它们。
  2. 在分配这些值时,为了方便起见,我需要查看结构成员名称。
  3. 为方便起见,我需要将所有默认值直观地显示在同一位置。
  4. 编译器目前优化了 myClass::addStruct() 中未使用的 if 分支 - 这正是我想要的。

问:能否以更好/更简单的方式完成?

编辑我没有 C++17

// 外部结构

typedef struct A {
    int member1;
    float member2;
    char *member3;
    // ...
} A;

typedef struct B {
    double member5;
    float member3;
    int *member4;
    // ...
} B;

typedef struct C {
    char* member5;
    char* member2;
    float *member3;
    // ...
} C;

...

template <class T>
void myClass::addStruct(std::vector<T> &vp)
{
    void *sp = nullptr;
    if(std::is_same<T, A>::value) {
        A s{};
        s.member1 = 2;
        s.member3 = "whatever";
        sp = &s;
    }
    else if(std::is_same<T, B>::value) {
        B s{};
        s.member4 = nullptr;
        s.member3 = 3.1f;
        sp = &s;
    }
    else if(std::is_same<T, C>::value) {
        C s{};
        s.member2 = "whenever";
        sp = &s;
    }
/*  else if() {
    }
    else if() {
    } ...
*/

    if(sp == nullptr) {
        // print error
        return;
    }

    vp.push_back(*(reinterpret_cast<T*>(sp)));
}

// usage

addStruct<A>(...);
addStruct<A>(...);
addStruct<B>(...);
addStruct<C>(...);

【问题讨论】:

  • 尝试显式模板专业化
  • 悬空指针应该/可能/会给你带来问题。为避免,您必须在堆上创建对象。
  • @xaxxon 因为没有 if constexpr 就无法编译;)
  • @Trass3r 如果您将向量的转换放在 if 它应该工作的每个分支中。将任何类型转换为类型 T.. 但只有在正确时才会真正发生。

标签: c++ visual-c++ factory


【解决方案1】:
  1. 除非您使用 if constexpr(也称为“静态 if”),否则此类代码甚至不应该编译。它只会“起作用”,因为您破坏了类型系统(整个 void* 和 reinterpret_cast 舞蹈)。 Type aliasing rules 申请。
  2. 您正在获取已经超出范围的对象的地址。那是Undefined Behavior,现在工作时,在更改代码或编译器或只是编译器版本时可能随时中断。
  3. 如果您真的想使用该代码,为什么不为不同类型创建非模板重载?更简单。

【讨论】:

  • 它编译什么都没说 - 你正在使用 void* 转义类型系统。它的工作原理没有说明 - 未定义的行为还包括“一切正常(目前)”的可能性。在这种情况下,很明显您处于 UB 领域:sp 指向一个堆栈变量(例如 A s),当您执行 push_back 时,该变量已 100% 超出范围。
  • @Max 究竟是什么导致了代码中未定义的行为?
  • @user1481126 取消引用指向不再存在的对象的指针。
  • 这部分*(reinterpret_cast&lt;T*&gt;(sp))。它甚至可能是双 UB,因为我认为它也打破了严格的别名
  • @Trass3r 这与取消引用您将一些回调用户数据放入的void* 有何不同? Afaik 那不是 UB...
【解决方案2】:
#include <vector>

typedef struct A {
    int member1;
    float member2;
    const char *member3;
    // ...
} A;

typedef struct B {
    double member5;
    float member3;
    int *member4;
    // ...
} B;

typedef struct C {
    char* member5;
    char* member2;
    float *member3;
    // ...
} C;

template <class T>
void addStruct(std::vector<T> &vp)
{
    // print error
}

template <>
void addStruct<A>(std::vector<A> &vp)
{
    A s{};
    s.member1 = 2;
    s.member3 = "whatever";
    vp.push_back(s);
}

template <>
void addStruct<B>(std::vector<B> &vp)
{
    B s{};
    s.member4 = nullptr;
    s.member3 = 3.1f;
    vp.push_back(s);
}

// ...

Trass3r 之后的 UPD:

#include <vector>

typedef struct A {
    int member1;
    float member2;
    const char *member3;
    // ...
} A;

typedef struct B {
    double member5;
    float member3;
    int *member4;
    // ...
} B;

typedef struct C {
    char* member5;
    char* member2;
    float *member3;
    // ...
} C;

void addStruct(std::vector<A> &vp)
{
    A s{};
    s.member1 = 2;
    s.member3 = "whatever";
    vp.push_back(s);
}

void addStruct(std::vector<B> &vp)
{
    B s{};
    s.member4 = nullptr;
    s.member3 = 3.1f;
    vp.push_back(s);
}

// ...

【讨论】:

  • 是的,正如@Trass3r 提到的,它可以在没有模板的情况下完成
  • 你应该是 std::move()'ing 进入向量。或者只是默认直接在向量中构建它,然后将其填充到位 - 那时根本不需要移动。
  • @xaxxon 实际上没有什么可以移动(我希望 =)),默认在向量中构造 - 看起来是一种最佳方式
  • auto &amp; s = v.emplace_back(); -- 哎呀,他没有 c++17...v.emplace_back(); auto &amp; s = v.back();
  • @xaxxon emplace 版本是否保证对对象进行零初始化?
【解决方案3】:

创建工厂方法:

template <typename T> struct Tag {};

A MakeDefault(Tag<A>) {
    A a{};
    a.member1 = 2;
    a.member3 = "whatever";
    return a;
}

B MakeDefault(Tag<B>) {
    B b{};
    b.member4 = nullptr;
    b.member3 = 3.1f;
    return b;
}

C MakeDefault(Tag<C>) {
    C c{};
    c.member2 = "whenever";
    return c;
}

// ...

template <class T>
void myClass::addStruct(std::vector<T> &vp)
{
    vp.push_back(MakeDefault(tag<T>{}));
}

我使用标签调度而不是模板专业化,这在这里看起来更简单。

【讨论】:

    【解决方案4】:

    问:能否以更好/更简单的方式完成?

    是的:它可以在没有模板的情况下通过使用函数多态性(具有相同名称和不同参数的函数)来完成。

    typedef struct A {
        int member1;
        float member2;
        char *member3;
        // ...
    } A;
    
    typedef struct B {
        double member5;
        float member3;
        int *member4;
        // ...
    } B;
    
    ...
    
    void myClass::addStruct(std::vector<A>& v)
    {
        v.push_back({0});
        v.back().member1 = 2;
        v.back().member3 = "whatever";
    }
    
    void myClass::addStruct(std::vector<B>& v)
    {
        v.push_back({0});
        // v.back().member4 = nullptr; // this is unnecessary now
        v.back().member3 = 3.1f;
    }
    
    ...
    
    // usage
    
    std::vector<A> va;
    std::vector<B> vb;
    
    myClass c;
    c.addStruct(va);
    c.addStruct(vb);
    

    【讨论】:

    • 解释你的答案,而不仅仅是转储代码。
    猜你喜欢
    • 1970-01-01
    • 2012-09-14
    • 1970-01-01
    • 2018-10-21
    • 1970-01-01
    • 2021-07-15
    • 2017-09-12
    • 2021-11-28
    • 1970-01-01
    相关资源
    最近更新 更多