Initialize C struct data member in a C++ wrapper class
The essential problem has basically already been answered elsewhere on this site, but what I really want is opinions on the best way to implement my class in terms of practicality and aesthetics, and if there are any subtleties involved. So bearing that in mind, here's my question:
I have a simple encryption program that I've written and now I want to add xz compression to it, which is written in C. The xz code uses a struct to control data in and out of the compression algos:
/* All of this is in src/liblzma/api/lzma/base.h if you download version 5.0.3
* XZ Utils
*/
typedef struct {
const uint8_t *next_in;
size_t avail_in;
uint64_t total_in;
/* ...
* and so on. Some other members are enums and other structs, but
* this is basically a POD structure
*/
} lzma_stream;
/* This macro is used to initialize lzma_stream objects */
#define LZMA_STREAM_INIT \
{ NULL, 0, 0, NULL, 0, 0, NULL, NULL, \
NULL, NULL, NULL, NULL, 0, 0, 0, 0, \
LZMA_RESERVED_ENUM, LZMA_RESERVED_ENUM }
/* Here's LZMA_RESERVED_ENUM in case anyone's wondering: */
typedef enum {
LZMA_RESERVED_ENUM = 0
} lzma_reserved_enum;
I have a wrapper class to lzma_stream so that if my encryption code throws, the wrapper class destructor can call functions that deallocate any assigned memory in the lzma_stream struct. So, I have:
class Stream {
public:
Stream();
~Stream();
void init();
// ...
private:
lzma_stream stream_;
// ...
};
Stream::~Stream() {
lzma_end( &stream_ );
}
My question is, how would you initialize Stream::stream_
and why? I could initialize the struct's members individually:
Stream::Stream() : stream_(), ... {}
void Stream::init() {
stream_.next_in = NULL;
开发者_JAVA技巧 stream_.avail_in = 0;
// ...
}
But I would like to use LZMA_STREAM_INIT
because that would mean that I would not need to worry about changes in the xz library. With that in mind, alternatively, I could create a temp:
Stream::Stream() : stream_(), ... {}
void Stream::init() {
lzma_stream const temp = LZMA_STREAM_INIT;
stream_ = temp;
// ...
}
Preliminary question: Is there a way I could do the initialization in the Stream
ctor (edit: I mean, in the initialization list)? (I take it not, right?) I'm trying to avoid c++0x initialization lists, by the way, for compiler portability reasons.
As I said above, these are they ways to solve the problem and that's already been said elsewhere; but what I'd like to know is which way would you guys do it (if there's not some other way I don't know about)? I can already guess that you'd say the latter method, but I have the sneaky feeling that there's a catch involved: is there?
OK, lots of useful info and solutions provided below. Thanks for all help, guys.
Yes, you can use the ctor-initializer, you just need to add a helper function:
Stream::Stream() : stream_(def_stream) {}
static lzma_stream def_stream()
{
lzma_stream tmpStream = LZMA_STREAM_INIT;
return tmpStream;
}
Among other things, this allows you to initialize a const aggregate member. And almost all compilers will elide creation of the temporary.
If you run into a compiler that doesn't, you can use this variation:
static const lzma_stream& def_stream()
{
static lzma_stream tmpStream = LZMA_STREAM_INIT;
return tmpStream;
}
In C++0x, you'll be able to write:
Stream::Stream() : stream_ LZMA_STREAM_INIT {}
which takes advantage of the "uniform initializer syntax".
Why not just put the initialization in the ctor?
Stream::Stream() : ...
{
lzma_stream const temp = LZMA_STREAM_INIT;
stream_ = temp;
// ...
}
Note that you don't need a mem-initializer for stream_
. When a POD member has no mem-initializer, C++ will leave it "uninitialized", just like a local variable lzma_stream stream;
. But as long as you then assign to it right away, that's not really a bad thing.
I also note that lzma itself recommends this "temp" object assignment pattern (in base.h) for cases when a direct initialization can't be done. In C, it's talking more about when the lzma_stream
memory was malloc
-ed, but it also applies to a C++03 class member. (C++0x does have a way of doing this in a mem-initializer, yes.)
Sure you could:
Stream::Stream() : your_init_list {
lzma_stream tmpStream = LZMA_STREAM_INIT;
stream_ = tmpStream;
}
A constructor has a body like any other function or method, and struct
s have implicit copy constructors and assignment operators. If you're using GCC, you can even skip the temporary and assign directly to the field.
Edit: It's not possible to construct a struct
in the init list unless it has an appropriate constructor defined (i.e. there is no implicit constructor that takes arguments for every field.) That's why the constructor has a body--so you can do setup for the object that goes beyond basic field assignment.
Edit #2: As Ben points out, you can use a helper function for this, but directly initializing the field in the init list (something like stream_(LZMA_STREAM_INIT)
) isn't possible.
精彩评论