练习1
union Some (int le)
{
int i[le];
float f[le];
};
练习2
union Some
{
int le;
int i[le];
float f[le];
};
阻止这项工作D: 也许是一种使用内部变量来设置长度的方法,但也不能起作用。 THX。
答案 0 :(得分:9)
不,这是不可能的:{em}需要在编译时知道。
一种解决方案是使用模板联合:
le
template <int N> union Some
{
int i[N];
float f[N];
};
当然是编译时可评估的。
另一个解决方案是可以说更简洁
N
或基于 typedef std::vector<std::pair<int, float>> Some;
的类似解决方案。
答案 1 :(得分:2)
根据您的使用案例,您可以尝试模拟联合。
struct Some
{
//Order is important
private:
char* pData;
public:
int* const i;
float* const f;
public:
Some(size_t len)
:pData(new char[sizeof(int) < sizeof(float) ? sizeof(float) : sizeof(int)])
,i ((int*)pData)
,f ((float*)pData)
{
}
~Some()
{
delete[] pData;
}
Some(const Some&) = delete;
Some& operator=(const Some&) = delete;
};
使用模板,unique_ptr和显式强制转换的替代解决方案:
//max_size_of<>: a recursive template struct to evaluate the
// maximum value of the sizeof function of all types passed as
// parameter
//The recursion is done by using the "value" of another
// specialization of max_size_of<> with less parameter types
template <typename T, typename...Args>
struct max_size_of
{
static const std::size_t value = std::max(sizeof(T), max_size_of<Args...>::value);
};
//Specialication for max_size_of<> as recursion stop
template <typename T>
struct max_size_of<T>
{
static const std::size_t value = sizeof(T);
};
//dataptr_auto_cast<>: a recursive template struct that
// introduces a virtual function "char* const data_ptr()"
// and an additional explicit cast operator for a pointer
// of the first type. Due to the recursion a cast operator
// for every type passed to the struct is created.
//Attention: types are not allowed to be duplicate
//The recursion is done by inheriting from of another
// specialization of dataptr_auto_cast<> with less parameter types
template <typename T, typename...Args>
struct dataptr_auto_cast : public dataptr_auto_cast<Args...>
{
virtual char* const data_ptr() const = 0; //This is needed by the cast operator
explicit operator T* const() const { return (T*)data_ptr(); } //make it explicit to avoid unwanted side effects (manual cast needed)
};
//Specialization of dataptr_auto_cast<> as recursion stop
template <typename T>
struct dataptr_auto_cast<T>
{
virtual char* const data_ptr() const = 0;
explicit operator T* const() const { return (T*)data_ptr(); }
};
//union_array<>: inherits from dataptr_auto_cast<> with the same
// template parameters. Also has a static const member "blockSize"
// that indicates the size of the largest datatype passed as parameter
// "blockSize" is used to determine the space needed to store "size"
// elements.
template <typename...Args>
struct union_array : public dataptr_auto_cast<Args...>
{
static const size_t blockSize = max_size_of<Args...>::value;
private:
std::unique_ptr<char[]> m_pData; //std::unique_ptr automatically deletes the memory it points to on destruction
size_t m_size; //The size/no. of elements
public:
//Create a new array to store "size" elements
union_array(size_t size)
:m_pData(new char[size*blockSize])
,m_size(size)
{
}
//Copy constructor
union_array(const union_array<Args...>& other)
:m_pData(new char[other.m_size*blockSize])
,m_size(other.m_size)
{
memcpy(m_pData.get(), other.m_pData.get(), other.m_size);
}
//Move constructor
union_array(union_array<Args...>&& other)
:m_pData(std::move(other.m_pData))
,m_size(std::move(other.m_size))
{
}
union_array& operator=(const union_array<Args...>& other)
{
m_pData = new char[other.m_size*blockSize];
m_size = other.m_size;
memcpy(m_pData.get(), other.m_pData.get(), other.m_size);
}
union_array& operator=(union_array<Args...>&& other)
{
m_pData = std::move(other.m_pData);
m_size = std::move(other.m_size);
}
~union_array() = default;
size_t size() const
{
return m_size;
}
//Implementation of dataptr_auto_cast<>::data_ptr
virtual char* const data_ptr() const override
{
return m_pData.get();
}
};
int main()
{
auto a = union_array<int, char, float, double>(5); //Create a new union_array object with enough space to store either 5 int, 5 char, 5 float or 5 double values.
((int*)a)[3] = 3; //Use as int array
auto b = a; //copy
((int*)b)[3] = 1; //Change a value
auto c = std::move(a);// move a to c, a is invalid beyond this point
// std::cout << ((int*)a)[3] << std::endl; //This will crash as a is invalid due to the move
std::cout << ((int*)b)[3] << std::endl; //prints "1"
std::cout << ((int*)c)[3] << std::endl; //prints "3"
}
<强>解释强>
template <typename T, typename...Args>
struct max_size_of
{
static const std::size_t value = std::max(sizeof(T), max_size_of<Args...>::value);
};
template <typename T>
struct max_size_of<T>
{
static const std::size_t value = sizeof(T);
};
max_size_of<>
用于获取作为模板参数传递的所有类型的最大sizeof()
值。
让我们先看一下这个简单的案例。
- max_size_of<char>::value
:value
将设置为sizeof(char)
。
- max_size_of<int>::value
:value
将设置为sizeof(int)
。
- 等等
如果你输入多种类型,它将评估这些类型的sizeof
的最大值。
对于2种类型,它看起来像这样:max_size_of<char, int>::value
:value
将设置为std::max(sizeof(char), max_size_of<int>::value)
。
如上所述,max_size_of<int>::value
与sizeof(int)
相同,因此max_size_of<char, int>::value
与std::max(sizeof(char), sizeof(int))
相同,与sizeof(int)
相同。
template <typename T, typename...Args>
struct dataptr_auto_cast : public dataptr_auto_cast<Args...>
{
virtual char* const data_ptr() const = 0;
explicit operator T* const() const { return (T*)data_ptr(); }
};
template <typename T>
struct dataptr_auto_cast<T>
{
virtual char* const data_ptr() const = 0;
explicit operator T* const() const { return (T*)data_ptr(); }
};
dataptr_auto_cast<>
是我们用作简单的抽象基类的。
它迫使我们在最后一个类中实现一个函数char* const data_ptr() const
(它将是union_array
)。
让我们假设该类不是抽象的,并使用简单版本dataptr_auto_cast<T>
:
该类实现一个操作符函数,该函数返回传递的模板参数类型的指针。
dataptr_auto_cast<int>
有一个函数explicit operator int* const() const;
该函数通过data_ptr()
函数提供对派生类提供的数据的访问,并将其强制转换为T* const
类型。
const
是指针不会被意外更改,explicit
关键字用于避免不必要的隐式转换。
正如您所看到的,dataptr_auto_cast<>
有2个版本。一个有1个模板参数(我们刚看过)和一个有多个模板参数。
该定义非常相似,只有多个参数一个继承dataptr_auto_cast
,其中一个(第一个)模板参数较少。
因此dataptr_auto_cast<int, char>
有一个函数explicit operator int* const() const;
并继承了dataptr_auto_cast<char>
,其函数为explicit operator char* const() const;
。
如您所见,您传递的每种类型都实现了一个强制转换操作符函数。
只有一个例外,即两次传递相同的模板参数。
这将导致相同的操作符函数在相同的类中被定义两次,这不起作用。
对于这个用例,使用它作为union_array
的基类,这应该不重要。
现在这两个很清楚,让我们看一下union_array
的实际代码:
template <typename...Args>
struct union_array : public dataptr_auto_cast<Args...>
{
static const size_t blockSize = max_size_of<Args...>::value;
private:
std::unique_ptr<char[]> m_pData;
size_t m_size;
public:
//Create a new array to store "size" elements
union_array(size_t size)
:m_pData(new char[size*blockSize])
,m_size(size)
{
}
//Copy constructor
union_array(const union_array<Args...>& other)
:m_pData(new char[other.m_size*blockSize])
,m_size(other.m_size)
{
memcpy(m_pData.get(), other.m_pData.get(), other.m_size);
}
//Move constructor
union_array(union_array<Args...>&& other)
:m_pData(std::move(other.m_pData))
,m_size(std::move(other.m_size))
{
}
union_array& operator=(const union_array<Args...>& other)
{
m_pData = new char[other.m_size*blockSize];
m_size = other.m_size;
memcpy(m_pData.get(), other.m_pData.get(), other.m_size);
}
union_array& operator=(union_array<Args...>&& other)
{
m_pData = std::move(other.m_pData);
m_size = std::move(other.m_size);
}
~union_array() = default;
size_t size() const
{
return m_size;
}
virtual char* const data_ptr() const override
{
return m_pData.get();
}
};
正如您所见,union_array<>
使用相同的模板参数从dataptr_auto_cast<>
继承。
因此,这为我们提供了作为模板参数传递给union_array<>
的每种类型的强制转换运算符。
同样在union_array<>
的末尾,您可以看到char* const data_ptr() const
函数已实现(来自dataptr_auto_cast<>
的抽象函数)。
下一个有趣的事情是static const size_t blockSize
,它使用模板参数的最大sizeof
值初始化为union_array<>
。
要获得此值,请使用max_size_of
,如上所述。
该类使用std::unique_ptr<char[]>
作为数据存储,因为std::unique_ptr
会在类被销毁后自动为我们删除空间。
同样std::unique_ptr
能够移动语义,它在移动赋值操作符函数和移动构造函数中使用。
还包括“普通”复制分配操作符函数和复制构造函数,并相应地复制内存。
该类有一个构造函数union_array(size_t size)
,它接受union_array
应该能够容纳的元素数。
将此值与blockSize
相乘可为我们提供存储最大模板类型的size
个元素所需的空间。
最后但并非最不重要的是,如果需要,有一种访问方法可以要求size()
。
答案 2 :(得分:1)
C ++要求在编译时知道类型的大小。
无需知道数据块的大小,但所有类型都有已知的大小。
围绕它有三种方法。
我暂时忽略了工会部分。想象一下,如果你想:
struct some (int how_many) {
int data[how_many];
};
因为联合部分增加了可以单独处理的复杂性。
首先,您可以将指针/引用/等存储到数据中,而不是将数据存储为类型的一部分。
struct some {
std::vector<int> data;
explicit some( size_t how_many ):data(how_many) {};
some( some&& ) = default;
some& operator=( some&& ) = default;
some( some const& ) = default;
some& operator=( some const& ) = default;
some() = default;
~some() = default;
};
这里我们将数据存储在std::vector
- 动态数组中。我们默认复制/移动/构造/破坏操作(显式 - 因为它使它更清晰),并且正确的事情发生。
我们可以使用unique_ptr代替向量:
struct some {
std::unique_ptr<int[]> data;
explicit some( size_t how_many ):data(new int[how_many]) {};
some( some&& ) = default;
some& operator=( some&& ) = default;
some() = default;
~some() = default;
};
这会阻止对结构的复制,但在典型的std
实现中,结构从3个指针的大小变为1的大小。我们失去了在事后轻松调整大小的能力,并且无需自己编写代码即可复制。
下一个方法是模板化。
template<std::size_t N>
struct some {
int data[N];
};
但是,这需要在编译时知道结构的大小,some<2>
和some<3>
是不相关的类型&#39; (禁止模板模式匹配)。所以它有缺点。
最后一种方法是类似C的。在这里,我们依赖于数据大小可变的事实,即使类型不是。
struct some {
int data[1]; // or `0` in some compilers as an extension
};
some* make_some( std::size_t n ) {
Assert(n >= 1); // unless we did `data[0]` above
char* buff = new some[(n-1)*sizeof(int) + sizeof(some)]; // note: alignment issues on some platforms?
return new(buff) some(); // placement new
};
我们为可变大小的some
分配缓冲区。通过data[13]
访问缓冲区实际上是合法的,实际上可能也是如此。
这种技术在C中用于创建可变大小的结构。
对于union部分,您将要创建一个char
的缓冲区,其大小为std::max(sizeof(float), sizeof(int))*N
,并公开函数:
char* data(); // returns a pointer to the start of the buffer
int* i() { return reinterpret_cast<int*>(data()); }
float* f() { return reinterpret_cast<float*>(data()); }
您可能还需要将数据正确初始化为正确的类型;从理论上讲,char
s的'\0'
缓冲区可能与定义的float
值或int
s不对应。
答案 3 :(得分:1)
我想建议一种不同的方法:不要将元素数量与union
联系起来,而是将其绑定在外面:
union Some
{
int i;
float f;
};
Some *get_Some(int le) { return new Some[le]; }
不要忘记delete[]
get_Some
的返回值...或者使用智能指针:
std::unique_ptr<Some[]> get_Some(int le)
{ return std::make_unique<Some[]>(le); }
您甚至可以创建Some_Manager
:
struct Some_Manager
{
union Some
{
int i;
float f;
};
Some_Manager(int le) :
m_le{le},
m_some{std::make_unique<Some[]>(le)}
{}
// ... getters and setters...
int count() const { return m_le; }
Some &operator[](int le) { return m_some[le]; }
private:
int m_le{};
std::unique_ptr<Some[]> m_some;
};
查看 Live example 。
答案 4 :(得分:0)
无法声明具有动态大小的结构,正如您尝试的那样,必须在运行时指定大小,否则您将不得不使用更高级别的抽象来管理动态池在运行时的内存。
此外,在您的第二个示例中,您在联合中包含le
。如果您尝试执行的操作是可能的,则会导致le
与i
和f
的第一个值重叠。
如前所述,如果在编译时已知大小,则可以使用模板执行此操作:
#include <cstdlib>
template<size_t Sz>
union U {
int i[Sz];
float f[Sz];
};
int main() {
U<30> u;
u.i[0] = 0;
u.f[1] = 1.0;
}
如果你想要动态尺寸,你就会开始进入最好使用像std::vector
这样的东西。
#include <vector>
#include <iostream>
union U {
int i;
float f;
};
int main() {
std::vector<U> vec;
vec.resize(32);
vec[0].i = 0;
vec[1].f = 42.0;
// But there is no way to tell whether a given element is
// supposed to be an int or a float:
// vec[1] was populated via the 'f' option of the union:
std::cout << "vec[1].i = " << vec[1].i << '\n';
}