3

我经常使用包含 20 多个字段的大型结构,这些字段需要用不同的值进行初始化。每次写 init 函数的时候,我都心存疑虑,总是担心我错过了一个被赋值的字段。所以我不得不一一检查每个字段。

我讨厌这个,所以我使用 CHECK_VAL 宏,如示例代码。现在如果我在结构初始化中遗漏了一项,编译器会报错:

“Check”类型的值不能用于初始化“int”类型的实体

我的问题:是否有其他方法可以帮助解决我的问题?语言是 C 和 C++,大结构是 POD 类型。

代码示例

#define DOCHECK 1
#if DOCHECK
typedef struct _Check{
    char k;
} Check;
Check g_check = {'c'};
#define CHECK_DEL Check c1234567;
#define CHECK_VAL (g_check)
#else
#define CHECK_DEL
#define CHECK_VAL
#endif

typedef struct _BigStruct{
    int bar;
    int foo;
    /*...*/
    int f99;
    int f100;
    CHECK_DEL;
}BigStruct;

void initBigStruct(BigStruct* p){
    int a,b,c,d;
    a = b = c = d = 0;
    /*
        many other code to caculate the value of a,b,c,d
    */
    {
        BigStruct tmp = {a,b,c,d, CHECK_VAL};
        *p = tmp;
    }
}
4

3 回答 3

5

从语言的角度来看,可能不是很多。

但是,GCC 有-Wmissing-field-initializers针对这种情况的标志。我确信其他编译器也提供类似的东西。

于 2013-01-09T02:44:25.563 回答
3

如果您在谈论 C++,您可以只为该类编写一个构造函数,使用您想要的任何内容进行初始化。但是,这当然会取消 POD 您的数据并阻止使用{..}.

如果是 C,您可以编写一个工厂方法,该方法返回一个已初始化的结构,如 @Pubby 建议的那样。

如果计算变量的数量让您感到困扰,您可以使用命名初始化,就像在使用标签的 C 结构初始化中一样。它有效,但如何?文档?

于 2013-01-09T02:44:47.547 回答
0

这个问题发布多年后,我在嵌套结构中遇到了同样的问题。我提出了一个超出 POD 结构范围的替代答案。然而,它确实解决了确保分配数据层次结构的所有字段的主要问题。该解决方案使用 std::optional 和 std::tuple 并且可以处理嵌套的数据层次结构。

考虑

struct Aggregate2 {
   struct Aggregate1 {
     int DataType1;
     float DataType2;      
   } aggregate1
   size_t DataType3;
 } aggregate2;

并比较

using Aggregate1 = std::tuple<std::optional<int>, std::optional<float>>;
using DataType3 = size_t;  
std::tuple<std::optional<Aggregate1>, std::optional<DataType3>> aggregate2;

从技术上讲,这可以存储所需的数据,但获取和设置将不可读,并且分配检查不会轻松自动。

这些问题在这里可以说是通过权衡保持层次结构的类型的定义不如 struct 方式可读性来解决的。下面的代码使用 MSVC 和 gcc 编译。它包含有关如何使用它的详细说明。

//struct_alternative.h
#include <tuple>
#include <optional>

// C++17 template variable to determine if type is std::tuple.
template <typename T>
constexpr bool isTuple = false;
template<typename ... Types>
constexpr bool isTuple<std::tuple<Types...>> = true;

// Get last type of std::tuple
template<typename ...T>
using LastEntityType = std::tuple_element_t<sizeof...(T) - 1, std::tuple<T...>>;

// Class that inherits all members of D.
// Constructor parses that data tree and throws if any instance of D has unassigned Data::data.
template<typename D>
class AssignedData: public D {
public:
    explicit AssignedData(D&& d) : D(std::move(d)) {
        if constexpr (isTuple<typename decltype(D::data)::value_type>) {
            std::apply([&](auto&&... args) {((args.throwIfNotAssigned()), ...);}, *d.data);
        } else {
            d.throwIfNotAssigned();
        }
    }
};

//
// Data is a template class with capability of storing a hierarchy (tree-like structure) of tuple data.
// The template argument represents the type of the data that is stored in an std::optional<T> 
// It has a set and get functinality. 
// 
// Use as follows:
//
// Define data classes that inherit from Data.
//
// class DataType1 : public Data<int>{};
// class DataType2 : public Data<float>{};
//
// Construct aggregate data types where the template argumets can be a combination of tuples of previously
// defined data types and new data types.
//
// class DataType3 : public Data<size_t>{};
// class Aggregate1 : public Data<std::tuple<DataType1, DataType2>> 
// class Aggregate2 : public Data<std::tuple::<Aggregate1, DataType3>>{};
//
// Create intsances of the Aggregate data type and assign the members.
//
// Arrgregate2 aggregate2;
// aggregate2.set<Aggregate1, DataType1>(1); // Will assigne the value 1 to DataType1 of aggregate2::Aggregate1. 
//
// Create an AssignedData object that guarantees that all members are assigned.
//
// auto assigned = AssignedData(std::move(aggregate)); // Will throw when not all data members are assigned.
//
// Get data member through
//
// int dataType1 = assigned.get<DataType4, DataType1>;
//
template <typename T>
class Data {
public:
    Data() {
        if constexpr(isTuple<T>) {
            // Make sure that all tuples are assigned.
            // If not done, Data::data which is of type std::optional<std::tuple<A, B ...>>
            // can get the tuple members (A, B ...) assigned but the std::optional<std::tuple<A, B...>>
            // will not have a value i.e. is an empty std::optional. This becomes a problem when traversing the Data tree.
            data = std::make_optional<T>();
        }
    }
    
    // Throw if any member of Data::data is not assigned i.e. is an empty optional.
    void throwIfNotAssigned() const {
        if (data.has_value()) {
            if constexpr (isTuple<T>) {
                std::apply([&](auto&&... args) {((args.throwIfNotAssigned()), ...);}, *data);
            }
        } else {
            throw(std::runtime_error("Data::data is not set."));
        }
    }

    // Get value of the data type corresponding to the last element of (First, ...U)
    template <typename First, typename ...U>
    auto get() const {
        if constexpr(isTuple<typename decltype(data)::value_type>) {
            if constexpr (sizeof...(U) > 0) {
                return std::get<First>(*data).template get<U...>();
            } else if (std::get<First>(*data).data.has_value()){
                return std::get<First>(*data).data.value();
            }
        } else if (data.has_value()) {
            return data.value();
        } 
        throw(std::runtime_error("Trying to get a Data::data that is not set."));
    }

    // Set value of the data type corresponding to the last element of (First, ...U)
    template<typename First, typename ...U>
    void set(const typename decltype(LastEntityType<First, U...>::data)::value_type& rhs) {
        if constexpr(isTuple<typename decltype(data)::value_type>) {
            if constexpr (sizeof...(U) > 0) {
                std::get<First>(*data).template set<U...>(rhs);
            } else 
                std::get<First>(*data).data = rhs;
        } else {
            data = rhs;
        }
    }

    std::optional<T> data;
};
于 2022-02-05T13:24:37.497 回答