non-const ref to temp object in member initialization

J

Jay

Hi,

I know the standard disallows the following for non-const X&
parameters, i,e,
void func(X&);
... func(X()); ...

However, I can see a reasonable use for this pattern in constructor
initialization lists, e.g.

class C {
public:
C(const char* fSpec): abc_(InFile(fSpec)) {}

private:
ABC abc_;
}

InFile has non-const methods to read data from a file. However,
because of the above language restriction, I have to either use
const_cast on InFile, or go through all sorts of convolusions, making
the code unnecessarily complicated. Would it be considered bad style
to use const_cast on InFile in the above case, or is there a better
workaround?

Thanks.
 
Ö

Öö Tiib

I know the standard disallows the following for non-const X&
parameters, i,e,
  void func(X&);
  ... func(X()); ...

C++ language lacks ways to mark that something passed in is a
temporary and may not be stored for future usage nor used as l-value.
The current solution that it must be "X const &" there is misleading
as well. Even as "X const &" it is temporary and may not be stored, it
only blocks its usage as l-value.
However, I can see a reasonable use for this pattern in constructor
initialization lists, e.g.

class C {
public:
 C(const char* fSpec): abc_(InFile(fSpec)) {}

private:
 ABC abc_;

}

InFile has non-const methods to read data from a file.  However,
because of the above language restriction, I have to either use
const_cast on InFile, or go through all sorts of convolusions, making
the code unnecessarily complicated.  Would it be considered bad style
to use const_cast on InFile in the above case, or is there a better
workaround?

There are always more elegant workarounds than const_cast. Lets
see ... for example some char* walking proudly around is smelly as
well. What about getting rid of both:

class C
{
public:
C( InFile& if )
: abc_( if )
{}

private:
ABC abc_;
}
 
J

Jay

There are always more elegant workarounds than const_cast. Lets
see ... for example some char* walking proudly around is smelly as
well. What about getting rid of both:

 class C
 {
 public:
     C( InFile& if )
             : abc_( if )
     {}

 private:
     ABC abc_;
 }- Hide quoted text -

- Show quoted text -

Perhaps I oversimplified my exmaple too much. The actual situation is
more like

class C
{
public:
C( unsigned id )
: abc_( InFile( privateMappingFromIdToFSpec(id) ) )
{}

private:
static const char* privateMappingFromIdToFspec(unsigned);

private:
ABC abc_;
}

The only thing client code knows is the 'id'. It doesn't need to know
that a file is even involved in initialization, let alone the file
name. Thanks.
 
Ö

Öö Tiib

Perhaps I oversimplified my exmaple too much.  The actual situation is
more like

class C
{
public:
  C( unsigned id )
          : abc_( InFile( privateMappingFromIdToFSpec(id) ) )
  {}

private:
  static const char* privateMappingFromIdToFspec(unsigned);

private:
  ABC abc_;

}

The only thing client code knows is the 'id'.  It doesn't need to know
that a file is even involved in initialization, let alone the file
name.  Thanks.

The real problem is that no one seemingly wants to take the ownership
and responsibility of that Infile and so it is implementation
temporary in somewhere between. Either ABC has to be responsible that
everything is fine with it or C has to take responsibility or someone
passing it to C has to be responsible.
 
J

Jay

The real problem is that no one seemingly wants to take the ownership
and responsibility of that Infile and so it is implementation
temporary in somewhere between. Either ABC has to be responsible that
everything is fine with it or C has to take responsibility or someone
passing it to C has to be responsible.- Hide quoted text -

- Show quoted text -

Well, from my understanding, this is the sequence of events:
- privateMappingFromIdToFSpec gets called and returns
- InFile constructor gets called (opens file) and returns
- abc_ constructor gets called, uses temporary InFile object, and
returns
- InFile destructor gets called (closes file)

InFile only needs to exists for the duration of the abc_ contructor.
It is created expressly for the abc_ contructor, and cleans up right
after it returns -- perfect example of RAII.
 
Ö

Öö Tiib

Well, from my understanding, this is the sequence of events:
- privateMappingFromIdToFSpec gets called and returns
- InFile constructor gets called (opens file) and returns
- abc_ constructor gets called, uses temporary InFile object, and
returns
- InFile destructor gets called (closes file)

InFile only needs to exists for the duration of the abc_ contructor.
It is created expressly for the abc_ contructor, and cleans up right
after it returns -- perfect example of RAII.

Yes. RAII works. But the InFile feels like a ... file for me. That
means usual set of "no such file", "network drive not available", "no
floppy in that drive", "no read acces on that device", "unexpected end
of file" and so on ... concerns. Who reports them to whom, who is
responsible and who takes corrective actions? It can not just crash on
case of problems with file?

I did not imply that it should be data member of some class, since
file object is needed temporarily it can be local variable in a
function that takes responsibility and trys to open it.
 
J

Jay

Yes. RAII works. But the InFile feels like a ... file for me. That
means usual set of "no such file", "network drive not available", "no
floppy in that drive", "no read acces on that device", "unexpected end
of file" and so on ... concerns. Who reports them to whom, who is
responsible and who takes corrective actions? It can not just crash on
case of problems with file?

I did not imply that it should be data member of some class, since
file object is needed temporarily it can be local variable in a
function that takes responsibility and trys to open it.- Hide quoted text -

- Show quoted text -

Exceptions are not a problem. If InFile's constructor throws an
exception, then InFile won't be created and C's constructor will
unwind. If there is a read error within ABC's contructor, then it
will throw an exception and InFile destructor will get called as C's
constructor unwinds. In either case, C won't get created and C's
client code can decide how to react.

I am not quite sure how a function would fit in here. The InFile has
to be created within C's initializer list because of the following two
invariants:
- C's clients don't know about files; they just pass an id and expect
a C.
- ABC's constructor expects an InFile given to it. It doesn't want to
be bothered opening a file itself, since it doesn't care whether it is
reading from a socket or a pipe or a disk file.

That means the only place to map from an id to an InFile is in C's
constructor. If abc_ is an embedded member of C, then I have to use
an initializer list. The only other option is to make it a pointer
(ABC*), and initialize it (along with InFile) in the body of C's
constructor. I don't like that option because then I get into all the
ugliness and overhead of memory management and also lose locality.
 
Ö

Öö Tiib

Exceptions are not a problem.  If InFile's constructor throws an
exception, then InFile won't be created and C's constructor will
unwind.  If there is a read error within ABC's contructor, then it
will throw an exception and InFile destructor will get called as C's
constructor unwinds.  In either case, C won't get created and C's
client code can decide how to react.

I am not quite sure how a function would fit in here.  The InFile has
to be created within C's initializer list because of the following two
invariants:
- C's clients don't know about files; they just pass an id and expect
a C.
- ABC's constructor expects an InFile given to it.  It doesn't want to
be bothered opening a file itself, since it doesn't care whether it is
reading from a socket or a pipe or a disk file.

Exactly. With all such there may be various recoverable or retryable
"connection reset by peer" trouble. The file failure exceptions seem
to be thrown to C's client by your design but at other place you claim
that they do not even know about usage of files there. Do not throw
exceptions to someone about what he is not responsible. It would be
fair feature request that if network hiccups you retry 3 times to
produce ABC and now please display how you maintain your code to
satisfy that request?
That means the only place to map from an id to an InFile is in C's
constructor.  If abc_ is an embedded member of C, then I have to use
an initializer list.  The only other option is to make it a pointer
(ABC*), and initialize it (along with InFile) in the body of C's
constructor.  I don't like that option because then I get into all the
ugliness and overhead of memory management and also lose locality.

Oh no, there are lot more ways. For example a function that takes "id"
or "fspec" and returns ABC. That function is responsible for trying to
manage with InFile (or other sockets/pipes involved) and throws to
client happen only on cases when it diagnoses that it does not manage.
I am not sure if return value optimization applies here, but it
probably does not matter since bottleneck is likely in file I/O
anyway.
 
J

Jay

Exactly. With all such there may be various recoverable or retryable
"connection reset by peer" trouble. The file failure exceptions seem
to be thrown to C's client by your design but at other place you claim
that they do not even know about usage of files there. Do not throw
exceptions to someone about what he is not responsible. It would be
fair feature request that if network hiccups you retry 3 times to
produce ABC and now please display how you maintain your code to
satisfy that request?


Oh no, there are lot more ways. For example a function that takes "id"
or "fspec" and returns ABC. That function is responsible for trying to
manage with InFile (or other sockets/pipes involved) and throws to
client happen only on cases when it diagnoses that it does not manage.
I am not sure if return value optimization applies here, but it
probably does not matter since bottleneck is likely in file I/O
anyway.- Hide quoted text -

- Show quoted text -

Good points. My intent with the exceptions was more of a reporting
activity. About the only thing C's clients can do is to report
failure, giving the exception as the cause. They could try calling
C's constructor again, but other members of C may not be amenable to
reconstruction (e.g. if they already consumed their socket input).

I like your idea of using a function with RVO. VC++ seems to allow
such a function even though ABC forbids copy construction. It can be
a static method in ABC and can also implement retries, I suppose.
Thanks...
 
J

Jay

In your both of your examples, class C and class ABC both have to know
about the class InFile, but the only thing C does is create an InFile
object. I don't see why ABC needs an InFile factory in this case. Why
not:

class ABC {
public:
   ABC(const char* fSpec) {
      InFile(fSpec);
      //...
   }

};

const char* privateMappingFromIdToFspec(unsigned);
   // the above shouldn't be a private static in class C IMHO

class C {
public:
   C(unsigned id) : abc_(privateMappingFromIdToFSpec(id)) { }

};

With the above, only ABC needs to know about InFile.- Hide quoted text -

- Show quoted text -

Yes, your suggestion is similar to what Oo said. I can't modify ABC's
constructor, but I can add static helper methods.

ABC ABC::FromFSpec(const char* fSpec)
{
InFile file(fSpec);
return ABC(file);
}

I am surprised VC++ lets me write such a method for a class which
forbids copy construction. Hopefully it is not a VC++ quirk and is
portable. Thanks.
 
J

Jeff Flinn

See inline below:
Perhaps I oversimplified my exmaple too much. The actual situation is
more like

class C
{
public:
C( unsigned id )
// : abc_( InFile( privateMappingFromIdToFSpec(id) ) )

: abc_(abcFrom(id))
{}

private:
static const char* privateMappingFromIdToFspec(unsigned);

static ABC abcFrom(unsigned id)
{
Infile inFile( privateMappingFromIdToFSpec(id) );

return ABC(infile);
}
private:
ABC abc_;
}

The only thing client code knows is the 'id'. It doesn't need to know
that a file is even involved in initialization, let alone the file
name. Thanks.

ABC will need a copy constructor, but will typically be elided from a
release build.

Jeff
 
J

Jay

See inline below:








//         : abc_( InFile( privateMappingFromIdToFSpec(id) ) )

            : abc_(abcFrom(id))



    static ABC abcFrom(unsigned id)
    {
       Infile inFile( privateMappingFromIdToFSpec(id) );

       return ABC(infile);
    }



ABC will need a copy constructor, but will typically be elided from a
release build.

Jeff- Hide quoted text -

- Show quoted text -

Well, its a bit strange. VC++ rejects the static method if ABC
forbids copy construction, but allows it if ABC's base class forbids
copy construction. I would have thought the method would be illegal
in both cases...
 
J

James Kanze

On Jul 24, 10:21 pm, "Daniel T." <[email protected]> wrote:

[...]
Yes, your suggestion is similar to what Oo said. I can't
modify ABC's constructor, but I can add static helper methods.
ABC ABC::FromFSpec(const char* fSpec)
{
InFile file(fSpec);
return ABC(file);
}
I am surprised VC++ lets me write such a method for a class which
forbids copy construction. Hopefully it is not a VC++ quirk and is
portable.

If ABC forbids copy construction, an ABC can be neither passed
by value nor returned from a function.

There is on hack which can be used to avoid any const_cast:
provide a non-const member function in InFile which returns a
non-const reference, and use it, e.g.:

class InFile
{
//...
InFile& me();
};

class C
{
//...
C(int id) : abc_(InFile(mapId(id)).me()) {}
};

While a temporary cannot bind to a non-const reference, you can
call non-const member functions on it.

I'm not sure if that's really cleaner than a const_cast,
however.
 

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,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top