views:

247

answers:

7

I have a class with an object as a member which doesn't have a default constructor. I'd like to initialize this member in the constructor, but it seems that in C++ I can't do that. Here is the class:

#include <boost/asio.hpp>
#include <boost/array.hpp>

using boost::asio::ip::udp;

template<class T>
class udp_sock
{
    public:
        udp_sock(std::string host, unsigned short port);
    private:
        boost::asio::io_service _io_service;
        udp::socket _sock;
        boost::array<T,256> _buf;
};

template<class T>
udp_sock<T>::udp_sock(std::string host = "localhost",
  unsigned short port = 50000)
{
    udp::resolver res(_io_service);
    udp::resolver::query query(udp::v4(), host, "spec");
    udp::endpoint ep = *res.resolve(query);
    ep.port(port);
    _sock(_io_service, ep);
}

The compiler tells me basically that it can't find a default constructor for udp::socket and by my research I understood that C++ implicitly initializes every member before calling the constructor. Is there any way to do it the way I wanted to do it, or is it too "Java-oriented" and not feasible in C++?

I worked around the problem by defining my constructor like this:

template<class T>
udp_sock<T>::udp_sock(std::string host = "localhost",
  unsigned short port = 50000) : _sock(_io_service)
{
    udp::resolver res(_io_service);
    udp::resolver::query query(udp::v4(), host, "spec");
    udp::endpoint ep = *res.resolve(query);
    ep.port(port);
    _sock.bind(ep);
}

So my question is more out of curiosity and to better understand OOP in C++

A: 

MSVC compiler creates for you copy constructor and default constructor (parameterless) for you. If you create any non-default constructor, MSVC doesn't create default constructor and asks you to do this. Constructor with all optional arguments can be qualified by default constructor, because it is allowed not to pass any parameter into it.

Andrey
+1  A: 

I think that's one possible use case for boost::optional.

UncleBens
Beat me to it while I was writing my examples :p It's my preferred way to deal with this case.
Matthieu M.
This is the kind of solution I was looking for, but I'm choosing Matthieu's answer since he also provided a simple use case.
Kjir
A: 

In C++ it's preferable to initialize members in the initializer list, rather than the body of the constructor, so in fact you might consider putting other members in the initialization list

If you're thinking about creating a constructor that other ctors call, that's not available til c++0x (see inheriting constructors)

Liz Albin
A: 

If it's to initialize a variable during construction in a class's constructor the right way is:

template<class T>
udp_sock<T>::udp_sock(std::string host = "localhost", unsigned short port = 50000)
    :res(_io_service)
    ,query(udp::v4(), host, "spec")
    ,ep(*res.resolve(query))
    ,_sock(_io_service, ep)
{
}

Edit: Forgot to mention that 'res', 'query' and 'ep' should be part of the class. Another crude method (without having _sock as a pointer) is as given below:

template<class T>
udp_sock<T>::udp_sock(std::string host = "localhost", unsigned short port = 50000)
    :_sock(_io_service, udp::resolver(_io_service).resolve(udp::resolver::query(udp::v4(),host,"spec"))
{
}
Vite Falcon
But this requires making `res`, `query`, and `ep` class members and not local variables of the constructor, which changes the contents of his class and is not particularly elegant.
Tyler McHenry
I forgot to mention that. I've added that into the edit and added another constructor that doesn't need any of those variables to be a member variable. They are created at runtime and destroyed as soon as it's used. The main disadvantage of using this method is that an exception that occurs during construction will cause memory leak. The best method is to either use _sock as a pointer or a boost::scoped_ptr.
Vite Falcon
I thought about the second version on my own, but I found it to be not so elegant as I would like it to be, that's why I kept looking for better solutions.
Kjir
You have to make a trade-off in this case (I believe). It's either you create an ugly line of code that would make rest of your code look elegant or make a 'looking good' piece of code and end up using a pointer.Personally, I would go down the line of using a pointer because that's the 'correct' way of doing it. Your code would not leak memory (as mentioned in my previous comment) if it throws an exception inside the constructor.
Vite Falcon
A: 

I think your solution is the correct way to do things.

You can also postpone the creation of the object by making is pointer (however it changes the code and data type):

std::auto_ptr<udp::socket> _sock;

And then in body:

_sock.reset(new udp::soket(_io_service, ep));

But I think that your "workaround" is rather correct solution then workaround.

Artyom
I was going to suggest the same. But instead of std::auto_ptr i was going to suggest boost::scoped_ptr since he's already using boost libararies.
Vite Falcon
Pointers means heap usage overhead and screwed copy semantics... though `scoped_ptr` will at least expose the copy problems!
Matthieu M.
I just prefer using standard library where possible, especially when `scoped_ptr` is just "little bit" stripped `auto_ptr`. I never use one.
Artyom
@Mattheiu, `io_service` is already non-copyable... So it does not meter. In any case such class *should* be defined as non-copyable.
Artyom
I always try to propose generic solutions since this problem appears regularly :)
Matthieu M.
A: 

You could turn the _sock member into a smart pointer:

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/scoped_ptr.hpp>

using boost::asio::ip::udp;

template<class T>
class udp_sock
{
    public:
        udp_sock(std::string host, unsigned short port);
    private:
        boost::asio::io_service _io_service;
        boost::scoped_ptr<udp::socket> _sock_ptr;
        boost::array<T,256> _buf;
};

template<class T>
udp_sock<T>::udp_sock(std::string host = "localhost",
  unsigned short port = 50000)
{
    udp::resolver res(_io_service);
    udp::resolver::query query(udp::v4(), host, "spec");
    udp::endpoint ep = *res.resolve(query);
    ep.port(port);
    _sock_ptr.reset(new udp::socket(_io_service, ep));
}
Romulo A. Ceccon
This means heap allocation + problems of copy semantics. It's a lot of hurt.
Matthieu M.
Not so much in his case, since `boost::asio::io_service` is already non-copyable.
Romulo A. Ceccon
I thought about pointers too, but I wanted to avoid dealing with allocation/deallocation and all the potential problems linked with pointers. I try to avoid pointers unless they are the only solution or they provide clear speed advantages...
Kjir
+3  A: 

When you define a constructor, you have 2 ways to "initialize" attributes:

  • the initializer list
  • the constructor body

If you do not explictly initialize one of the attributes in the initializer list, it is nonetheless initialized (by calling its default constructor) for you...

So in essence:

class Example
{
public:
  Example();
private:
  Bar mAttr;
};

// You write
Example::Example() {}

// The compiler understands
Example::Example(): mAttr() {}

And this of course fails if the underlying type does not have a Default Constructor.

There are various ways to defer this initialization. The "standard" way would be to use a pointer:

class Example { public: Example(); private: Bar* mAttr; };

However I prefer using Boost.Optional combined with suitable accessors:

class Example
{
public: Example();
private:
  Bar& accessAttr() { return *mAttr; }
  const Bar& getAttr() const { return *mAttr; }
  boost::Optional<Bar> mAttr;
};

Example::Example() { mAttr = Bar(42); }

Because Boost.Optional means that there is no overhead on the allocation and no overhead on the dereferencing (the object is created in place) and yet carries the correct semantic.

Matthieu M.
Thanks for the quick sample using boost::optional, it saved me the time needed to read the documentation just to understand if it could help me or not. This is exactly what I had in mind!
Kjir
Don't hesitate to read it when you find some time. It's actually quite short as it's a simple utility, but there are some options (like in-place factory to build the object) that are worth checking.
Matthieu M.