如何测试结构的成员是否已定义(c ++)

时间:2018-04-17 14:52:10

标签: c++

我正在尝试测试在调用函数之前是否定义了声明为void指针的结构的成员。但是,似乎每当我向裸骨程序添加额外的东西时(例如最后的std :: cout),'testB'总是等于true。我无法改变'struct a'的声明方式,因为它是外部库的一部分。

那么,我如何测试声明为void指针的结构的成员实际上是否使用正确的类型定义?

示例结构

struct b { };

struct a {
   void *b;
};

我试图测试的示例代码

struct a myA;
struct b myB;
myA.b = &myB;
foo(myA);

失败的示例测试程序

#include <iostream>

int main() {

   struct a aa;
   struct b bb;
   // the following line is commented out so I'm expecting 'testB' to evaluate to false
   // aa.b = &bb;

   struct b *testB = static_cast<struct b*>(aa.b);

   if(testB) {
      std::cout << "is defined" << std::endl;
   } else {
      std::cout << "is not defined" << std::endl;
   }

   std::cout << "why does adding this line out result in testB equating to true" << std::endl;

   return 0;
}

3 个答案:

答案 0 :(得分:6)

这是一个有趣的问题,因为你提出了两个问题:

  • 某些类型T是否有名为b的成员?
  • 如果前一个是真的,那是void*类型的成员吗?

我们可以在编译时完成所有这些工作。

在您的解决方案中,您不应该尝试执行静态转换,因为您有违反别名规则的风险,并且您还假设静态成员首先出现。

我们可以使用简单的检测习惯来检测条件是否满足:

template<class T, class=void>
struct has_void_ptr_member_named_b_t : std::false_type{};

template<class T>
struct has_void_ptr_member_named_b_t<T, std::void_t<decltype(std::declval<T>().b)>> : 
    std::is_same<std::decay_t<decltype(std::declval<T>().b)>, void*>
{};
template<class T>
constexpr bool has_void_ptr_member_named_b_v = has_void_ptr_member_named_b_t<T>::value;

现在,对于任何给定类型has_void_ptr_member_named_b_v

,您可以询问bool哪些可以转换为T

例如:

struct b { };
struct a {
   void *b;
};
// ...

std::cout << std::boolalpha << has_void_ptr_member_named_b_v<a> << std::endl; // true
std::cout << std::boolalpha << has_void_ptr_member_named_b_v<b> << std::endl; // false

Demo

这使用C ++ 17作为其std:void_t,这很容易创建自己。如果你努力尝试,你可以在C ++ 11中完成所有这些操作。如果您希望使用预先制定的检测惯用语(当前非标准的),您也可以查看std::experimental::is_detected

正如@YSC在下面的评论中所指出的,也许你只想测试aa.b是否为非空?

您应该将其初始化为nullptr,因为作为基本类型(指针),它没有默认构造函数,并且保持未初始化状态:

struct a {
   void *b = nullptr;
};

你仍然应该避免你的投射,因为你冒着未定义行为的风险,而且你假设结构总是排成一行。

a aa;
b bb;
auto test = [&aa](){
    if (aa.b)
        std::cout << "aa.b is defined\n";
    else
        std::cout << "aa.b is null\n";
};
test(); // "aa.b is null"
aa.b = &bb;
test(); // "aa.b is defined"

Demo 2

答案 1 :(得分:1)

在尝试使用它之前,您需要确保初始化aa.b。确保这一点的最简单方法是更改​​a,以便在构造函数中初始化。

以下是一些方法

struct a
{
    void* b = nullptr; // data member with initialiser
}

struct a
{
    void* b;
    a() : b(nullptr) {} // user defined default constructor
}

struct a
{
    void* b;
    a(void* _b = nullptr) : b(_b) {} // user defined constructor with default parameter
}

答案 2 :(得分:1)

如果您无法修改A,则必须自行采取措施以确保其已初始化。也许一个Wrapper(Adapter)对象可以用作A的更好实现。这里,它默认在类体中初始化,所以你可以确定指针在未初始化时将为null。您可以在下面使用类型安全的包装器(如SafeA)代替所有代码中的“struct a”,然后在需要与定义它的库进行交互时使用get_a()。 (一个非常糟糕的界面,顺便说一下,但是你被卡住了。)

我不是在处理b *指向的对象的内存管理,这是你的问题。 :)

class SafeA {
private: 
    a aa{}; // will initialize internal pointer in aa to nullptr

public:
    SafeA() = default;
    SafeA(SafeA const &) = default;
    SafeA(b * bPtr) : aa{bPtr} { }
    SafeA& operator=(SafeA const&) = default;
    SafeA& operator=(b *ptr) {
       aa.b = ptr;
       return *this;
    }       
    a& get_a() { return aa; }
    a const& get_a() const { return aa; }
};

如果你有一个void *,并且它被初始化为nullptr,你可以判断它是null还是not-null,但你无法确定它是否“正确”初始化为指向正确的类型。那个接口的作者强迫你这完全是一个问题。如果在声明指针时没有初始化指针,则以后无法确定它是否包含有效地址或垃圾,因此您希望始终初始化它,并且无法确定指向它的类型。所以要小心并使用包装器来帮助防止对它进行无类型访问。