3

My problem statement is as below. There is a concept of "domain" which consists of many "subdomains". Now, those subdomains are domains in their own right. Following is the basic method I can do the things. I can probably use an auto_ptr or something, but lets leave it for now.

class Domain
{
private:
    Domain* subdomains;
}

However, I was able to compile and run the following program which I think does the same thing and gives me what I want.

#include <iostream>
#include <vector>
#include <string>

class Domain
{
private:
    std::string name_;
public:
    std::vector<Domain> subdomains;
    Domain(std::string name) : name_(name) {};
    std::string name() {return name_;}
    void addSubDomain(std::string subDomainName);
};

void Domain::addSubDomain(std::string subDomainName)
{
    subdomain.push_back(Domain(subDomainName));
}

int main()
{
    std::cout<<"Hello, World"<<std::endl;
    Domain domain("wow");
    domain.addSubDomain("wow-child");
    std::cout<<"Domain name is "<<domain.name()<<std::endl;
    std::cout<<"Subdomain name is "<<domain.subdomain[0].name()<<std::endl;
    return 0;
}

The output which I get on running this is

$./main 
Hello, World
Domain name is wow
Subdomain name is wow-child

My question is whether there are any pitfalls that I may have missed while implementing the following thing? Right now, there is nothing I can see. If there are no pitfalls, then this is the a really good solution to my problems.

EDIT

In case this is not a solution, then is there another solution that I can use which does not involve the management of raw pointers?

  • The structure you created is a tree, and there's nothing inherently wrong with trees. – David Feb 28 '12 at 22:18
  • Basically you are trying to represent some kind of tree data structure here. As the STL doesn't include any special classes to model these, using a collection of children is find, but I'm not sure whether `std::vector` is sensible here. `std::set` might be a better choice, as the subdomains have no particular order. – Niklas B. Feb 28 '12 at 22:19
  • Are you compiling with max possible warning level? – BЈовић Feb 28 '12 at 22:23
  • @NiklasB. thanks for the set suggestion. I read this question [0], and there was nothing there that suggested anything apart from pointers to be a solution to what the OP wanted. So, I was not sure if there existed any such solutions and thought I might have run into a lucky case of compilation where I may get runtime errors later when I am adding extra functionality to this classes. [0]: http://stackoverflow.com/questions/2706129/can-a-c-class-include-itself-as-an-attribute –  Feb 28 '12 at 22:24
  • @VJovic: no, I am not. I will and let you know the results –  Feb 28 '12 at 22:28
  • Make sure to never add a subdomain in the constructor, else you'll get that infinite recursion problem cropping up – tmpearce Feb 28 '12 at 22:29
  • @VJovic I compiled with -Wall and -Wextra options and still no warnings. –  Feb 28 '12 at 22:32
  • I have accepted the answer provided by Johannes. I was sorely expecting the compiler to issue at least a warning though. :-( –  Feb 28 '12 at 22:41

3 Answers3

6

This is undefined behavior because at the point you define the member, the class is not yet completely defined. At that point, std::vector<Domain> needs to be instantiated though, from the template std::vector<T> to a class std::vector<Domain> (to determine its size, among others). When that instantiation happens, the Standard requires the class to be completely defined.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • my code compiles completely and is even giving me the output as shown. g++ version 4.6.2 –  Feb 28 '12 at 22:25
  • @JayeshBadwaik That doesn't mean it's not UB – David Feb 28 '12 at 22:27
  • 3
    C++03: §17.4.3.6: "In certain cases … the C++ Standard Library depends on components supplied by a C++ program … the effects are undefined … if an incomplete type is used as a template argument when instantiating a template component" – Robᵩ Feb 28 '12 at 22:30
  • @JayeshBadwaik: If the compiler was required to spit out an error, it wouldn't be undefined behavior anymore. – netcoder Feb 28 '12 at 22:31
  • that is what I am asking and why I posted the question in the first place. I understand your explanation and it is what exactly perplexed me too, but I decided to try the construction anyway and see what happens. Can you please explain where it might create problems in the future? As I said in a comment to the question, I compiled with -Wall and -Wextra flags and still I am getting no warnings. –  Feb 28 '12 at 22:35
  • @netcoder: thanks, it renders my last post moot. sorry for that. –  Feb 28 '12 at 22:36
  • undefined behavior can cause problems just about everywhere, in very unpredictable places. avoid it! – tmpearce Feb 28 '12 at 22:39
  • @Johannes: thanks for the explanation. I overlooked the undefined part. I get your answer now. Probably, you might just want to edit your answer to include a line explicitly that since the behaviour is undefined, whether the program will compiler or not depends on which specific compiler you are using. Also, it would be helpful to specify the reference which has been given by Rob –  Feb 28 '12 at 22:40
  • 1
    I’ll add that the only reason it *can* work in this case is that the implementation of `vector` does not have a member of type `T` (only, e.g., `T*`). Still undefined, o’course. – Jon Purdy Feb 28 '12 at 22:47
2

As Johannes correctly points out, your program is undefined. But, your program would be perfectly valid if it did this:

class Domain {
    …
    std::vector<Domain*> subdomains;
    …
};

Of course, you'd have to manage the pointers. (Exception safety, RAII, Rule of Three, etc, etc, etc).

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Does `std::unique_ptr` invoke undefined behavior? – Robᵩ Feb 28 '12 at 23:00
  • Oops, of course. But `boost::shared_ptr` (on which `std::shared_ptr` is based) explicitly declares that `T` may be an incomplete type, so this behaviour must be consistent across compilers. I think the standard is broken on this count. There is no reason to make `C` undefined for incomplete `T` if `C` only uses `T` by pointer or reference. – Jon Purdy Feb 28 '12 at 23:07
  • 1
    @Rob: No, the type needs only be complete at the point where the destructor of `unique_ptr` is instantiated, so an out-of-class empty destructor should do the job. And the best thing is that you get a compiler error if the type is incomplete at the time the destructor is instantiated. – Xeo Feb 29 '12 at 10:34
0

After reading the other answers and many other questions from the stackoverflow. I would like to sum up the answer in my own words. Though Johannes has answered the question correctly, there is more to the answer that I would like to add. So, here it is.

  1. The behaviour of the code given in the question is undefined in C++03 across all types of usages due to reasons given by Johannes's answer. However, in C++11 standard, the requirement has been changed. The behavior is still undefined unless the class specifically allows it.

    C++11 $17.6.4.8 Other functions (...) 2. the effects are undefined in the following cases: (...) In particular - if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.

  2. The current examples works because of the specfic implementation of the std::vector which does not contain any attributes of the type T but only of the type T*. Hence, this is a case of lucky compilation, which may or may not always work depending on the containers.

  3. The solution to the problem is to currently use the Boost::Containers since they specifically support the incomplete types.

  4. The std::unique_ptr and std::shared_ptr pointer can be used as a replacement for the raw pointer model for management if one wishes, with little catches. The answer to that question is here. And one can see the detailed explanation here. So, if one needs a std::container, then he can use the container of pointers using the above pointers. However, there are some conditions which must be satisfied. Those conditions are described in the links given above.

All in all, the best way right now is to use the boost::containers. However, they introduce a dependency on boost. In case you want to avoid the dependency, then you can use the std::unique_ptr and std::shared_ptr. However, there are some conditions and limitations as to what can be done.

boost::shared_ptr also allows incomplete types to be used. But, that too has some limitations.

Community
  • 1
  • 1