Is const_cast ok in marshalling code?

C

coal

I've been thinking about how to build(receive) an object from a stream
if the object's type has a const data member. While it would be great
to have ctors that read from a stream, I think const data members are
a difficult obstacle to making progress in that direction. Another
option would be to use an ordinary function called Receive. Say we
have this class:

class Cd {
int const az_;

public:
int Send(Buffer*, int = 0) const;
int Receive(Buffer*);
};

The code I'm thinking about using with Receive is:

int
Cd::Receive(Buffer* buf)
{
if (!buf->Give(const_cast<int*>(&az_), sizeof(int))) {
buf->ews_.SetErrorWords(1, __FILE__, __LINE__);
return 0;
}
return 1;
}

(*Buffer is here http://home.seventy7.com/misc/Buffer.hh.)

That seems fine to me. However, in this thread, http://preview.tinyurl.com/2yw4aa,
Eugene Gershnik wrote,

"No this is not what I have meant. What I did mean was that if you
need to interoprate with const-less library that you know
won't change the data you can cast away const on the boundary....
This and implementing non-const function in terms of const one are the
only acceptable uses of const_cast."
(It helps if you put a comma after the word data.)

I guess the const_cast above wouldn't be what he considers an
acceptable use. Is that rule commonly held? Any ideas on how to
improve the implementation?

Thanks.

Brian Wood
Ebenezer Enterprises
www.webebenezer.net

If you are in the United States illegally, you need to leave.
 
I

Ian Collins

I've been thinking about how to build(receive) an object from a stream
if the object's type has a const data member. While it would be great
to have ctors that read from a stream, I think const data members are
a difficult obstacle to making progress in that direction. Another
option would be to use an ordinary function called Receive. Say we
have this class:
If an object is to be built by anything other than its constructor, it
can't have a const member.

Why do you want one?
 
C

coal

If an object is to be built by anything other than its constructor, it
can't have a const member.

I doubt you mean that this would be OK

int
Cd::Cd(Buffer* buf)
{
if (!buf->Give(const_cast<int*>(&az_), sizeof(int))) {
buf->ews_.SetErrorWords(1, __FILE__, __LINE__);
throw runtime_error("...");
}
}

Probably you want to use the initializer list.
Why do you want one?

I don't have a specific application that needs this. I hope to
improve the marshalling framework I'm working on so it can handle this
possibility.

Brian Wood
 
B

Bo Persson

* (e-mail address removed):
[Is const_cast ok in marshalling code?]

No.

Cheers, & hth.,

It helps, but why do you say that?

Using const_cast on something that is actually declared const is not
allowed. The compiler just might have stored it in read-only memory.

You are allowed to remove const on things like a const reference
parameter, *provided* that you know for a fact that the actual
parameter is always non-const. If you make a mistake here, you enter
Undefined Behavior land.


Bo Persson
 
R

Rolf Magnus

Bo said:
* (e-mail address removed):
[Is const_cast ok in marshalling code?]

No.

Cheers, & hth.,

It helps, but why do you say that?

Using const_cast on something that is actually declared const is not
allowed. The compiler just might have stored it in read-only memory.

However, it's quite unlikely that you find a compiler that stores one single
member of an object in read-only memory.
 
C

coal

Using const_cast on something that is actually declared const is not
allowed.

OK. The compiler I'm using, gcc 4.0.3, warns about the above
Receive function, but it builds it. I just tried compiling it
as a ctor and in that case it gives a hard error.
The compiler just might have stored it in read-only memory.

You are allowed to remove const on things like a const reference
parameter, *provided* that you know for a fact that the actual
parameter is always non-const. If you make a mistake here, you enter
Undefined Behavior land.

Any suggestions on how to do this without going into UB land?

Brian Wood
 
B

Bo Persson

Rolf said:
Bo said:
* (e-mail address removed):
[Is const_cast ok in marshalling code?]

No.

Cheers, & hth.,


It helps, but why do you say that?

Using const_cast on something that is actually declared const is
not allowed. The compiler just might have stored it in read-only
memory.

However, it's quite unlikely that you find a compiler that stores
one single member of an object in read-only memory.

Yes, but do you want the code to be 100% portable, or is 98, 95, or
53% good enough? It's your call.


Bo Persson
 
B

Bo Persson

OK. The compiler I'm using, gcc 4.0.3, warns about the above
Receive function, but it builds it. I just tried compiling it
as a ctor and in that case it gives a hard error.


Any suggestions on how to do this without going into UB land?

There is no general solution. Either you know from the context that it
will always work, or you will have to take some precautions. Exactly
what you have to do I don't know - as usual, it depends.

If nothing else works, you might have to use the baseball bat method.
Visit all users of your code, and do some code reviews. If some of
them don't meet the *well documented requirements*, hit them over the
head with your baseball bat. Eventually, all the code will conform.


Bo Persson
 
R

Rolf Magnus

Bo Persson wrote:

Yes, but do you want the code to be 100% portable, or is 98, 95, or
53% good enough? It's your call.

There is no 100% portable code beyond a trivial "hello world", and even that
isn't really, since it's implementation defined what happens to data
written to cout.
 
C

coal

There is no general solution. Either you know from the context that it
will always work, or you will have to take some precautions. Exactly
what you have to do I don't know - as usual, it depends.



If instead of class Cd We use this:

class Cd2 {
int const az_;
vector<string> co_;

public:
Cd2(int az, vector<string> co) : az_(az), co_(co) {}
};


The outline of the marshalling could be:
marshall data into an int called arg1,
marshall data into a vector<string> called arg2,
build a Cd2 using arg1 and arg2.

In other words, instead of the "stream constructor" having this form:
Cd2(Buffer*);

it could take each data member as input and use them in an
initializer list. I think that would work no matter how the class
members are declared. The constructor could be generated easily. One
question with that, though is will it be inefficient because the
vector of strings has to be copied? Perhaps the copy can be avoided,
but if not the ctor might look like this:

Cd2(int az, Buffer*) : az_(az)
{
use stream to assign data to co_
}

And I would document that const data members have to precede non-const
data members if a type is going to be used in marshalling.

If nothing else works, you might have to use the baseball bat method.
Visit all users of your code, and do some code reviews. If some of
them don't meet the *well documented requirements*, hit them over the
head with your baseball bat. Eventually, all the code will conform.

Yeah, it can be a challenge.

Brian Wood
 
C

coal

If instead of class Cd We use this:

class Cd2 {
  int const az_;
  vector<string> co_;

public:
  Cd2(int az, vector<string> co) : az_(az), co_(co) {}

};

The outline of the marshalling could be:
 marshall datainto an int calledarg1,
 marshall datainto a vector<string> called arg2,
  build a Cd2 usingarg1and arg2.

In other words, instead of the "stream constructor" having this form:
Cd2(Buffer*);

it could take each data member as input and use them in an
initializer list.  I think that would work no matter how the class
members are declared.  The constructor could be generated easily.  One
question with that, though is will it be inefficient because the
vector of strings has to be copied?  Perhaps the copy can be avoided,
but if not the ctor might look like this:

Cd2(int az, Buffer*) : az_(az)
{
  use stream to assign data to co_

}

And I would document that const data members have to precede non-const
data members if a type is going to be used in marshalling.

I compared the two approaches mentioned above in a simple test.
The first version was typically 40-50% slower than the version that
used an initializer list only to initialize const data
members.

Previously David Schwartz wrote about initializer lists,
"Where you have a choice, and where things are otherwise equal, you
should use an initializer list with 'complex' members. It avoids
creating the member in its default state only to change it to the
desired state later. For simple types, it would be unlikely to make
any difference."

Frequently that is sound advice, but in a marshalling context
making a complex data member const to avoid creating the member
in it's default state would probably be a blunder efficiency-wise.
He did add the "where things are otherwise equal" clause so he
may have anticipated some contexts where the advice wouldn't
apply. I think const data members should be used sparingly in
a marshalling context. If using a const data member improves the
design it should be considered. If the member potentially has
some heft to it data-wise, there is a trade-off between design sanity
and efficiency to be weighed.

I think the requirement to declare const data members before
non-const data members would have to be made on behalf of any library/
framework that intends to support efficient marshalling.

Brian Wood
Ebenezer Enterprises
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top