Re: [PATCH kvm-unit-tests 2/4] Introduce a C++ wrapper for the kvm APIs

Anthony Liguori
2010-11-28T23:13:45Z

On 11/28/2010 04:28 PM, Michael S. Tsirkin wrote:
>
>> But rather need to use ugly factory functions with all sorts of
>> DO_UPCAST. This is really unfriendly especially for writing test
>> cases.
>>
> Yes, I agree. Just moving memory allocation out of there
> will fix most of the ugliness.
>

So here's a short list of things I've been working on that I don't
believe have nice implementations in C.

1) Factories with string based parameters with natural constructor
arguments.

I want to have:

I8254(DriftMode mode)

or:

void i8254_init(I8524 *self, DriftMode mode)

And either be able to directly instantiate the object or create it with
the following command:

dev = create("i8524", "mode", "catchup", NULL);

In C++, I can do:

static Device *i8524_create(DriftMode mode)
{
return new I8524(mode);
}

//...
add_factory("i8524", i8524_create, "mode");

This works because we can use templates to figure out the argument type
to create and automatically set things up for the mode parameter.
Because of overloading, we can have variable arguments so this works for
1, 2, or 10 arguments. In C, at best you would have to do something like:

static Device *i8524_create(ParameterList *list)
{
DriftMode mode;
I8524 *i8524;
if (param_list_has(list, "mode")) {
// should we check the type?
mode = (DriftMode)param_get_as_int(list, "mode"); // or do we
teach param about DriftMode?
} else {
// should we error?
}
i8524 = malloc(sizeof(*i8524));
if (i8524) {
return &i8524->dev; // I have to know how to get to this
}
return NULL;
}

struct factory_info i8524_info = {
.args = (FactoryArgument[]){
{ "mode", "DriftMode" },
{ }
}
};

add_factory("i8524", i8524_create, i8524_info);

And there's so many bad decisions I can make in the process. Do I have
a mechanism to register new types external to the system and make all
types convertable?

My C++ example uses a template specialization to convert from types to
strings. It's simple and extensible. There's no list that needs to be
kept at run time because the compiler does all of the book keeping.

I can let the compiler figure out how to go from I8524 to Device *
because it knows how to.

The same is true for generic get/set properties. In C++, I can do
something like:

class I8524 : public PropertyAccessible {
public:
enum LinkStatus {
LINK_UP,
LINK_DOWN,
};
LinkStatus get_link_status(void);
void set_link_status(void);
};

template <>
I8254::LinkStatus type_from_str(std::string value)
{
if (value == "Down") {
return LINK_DOWN;
} else if (value == "Up") {
return LINK_UP;
}
throw new InvalidTypeConversionException("LinkStatus", value);
}

template <>
std::string type_to_str(I8254::LinkStatus status)
{
if (status == LINK_DOWN) {
return "Down";
} else {
return "Up";
}
}

I8524::I8524(void) {
this->add_property("link_status", &I8524::get_link_status,
&I8524::set_link_status);
}

And now I can do:

Device *dev = new I8524();

cout << dev->get("link_status") << endl;

And it will print: Down

Simple and elegant. The main class has strong error checking, adding
the property is absolutely painless, supporting new types is absolutely
painless. The actual class implementation never sees a string--ever.
There is only one place string->type conversion happens.

I'm not going to show the C version because I know what it involves and
I suspect you do too. It totally sucks and I don't want to spend my
Sunday evening writing it. And that's really my motivation for using
C++. I get tired of doing the work that the compiler should be doing
for me. I want to spend my time doing interesting things like writing
device model test cases instead of writing code to work around the fact
that the C type system is about as powerful as a Vespa.

Regars,

Anthony Liguori