16

I am learning c++, and I just got to the object oriented chapter. I have a question about creating objects inside if statements.

The problem I'm working on says to create a class that will display a report header. The class has a default constructor that sets the company name and report name to a generic thing, and also, if the user wants, has a constructor that takes two arguments (strings company name and report name).

The problem says, specifically, "A two-parameter default constructor should allow these [company and report names] to be specified at the time a new Report object is created. If the user creates a Report object without passing any arguments, use the default values. Otherwise, use user specified values for the names."

So my question is, how to create these objects? I understand how to create an object without any arguments (i.e. Report newobj;), and also with arguments (i.e. Report newobj(string string);). Basically, I get how to create these objects initially at the top of my main function. But is it possible to create them inside if statements based on user choices? Here is what I have so far and, obviously, it doesn't work:

#include <iostream>
#include <string>
#include "report.h"
using namespace std;

bool enter_company_name();           // return true if user wants to enter company name
bool print_form();              // return true if user wants to print in formatted output

int main()
{
  string company_name,
    report_name;
  bool name = false,
    format = false;

  name = enter_company_name();
  format = print_form();

  if (name)
    {
      cout << "Enter company name: ";
      getline(cin, company_name);
      cout << "Enter report name: ";
      getline(cin, report_name);
      Report header(company_name, report_name);  // THIS IS MY PROBLEM
    }
  else
      Report header;  // THIS IS MY PROBLEM

  if (format)
    header.print_formatted();
  else
    header.print_one_line();

  return 0;
}

bool enter_company_name()
{
  char choice;

  cout << "Do you want to enter a name?\n>";
  cin >> choice;

  if (choice == 'y' || choice == 'Y')
    return true;
  else
    return false;
}

bool print_form()
{
  char choice;

  cout << "Do you want to print a formatted header?\n>";
  cin >> choice;

  if (choice == 'y' || choice == 'Y')
    return true;
  else
    return false;
}

So I want to create an object using default values if none are specified, or create one with the user values if that's the choice given. I just can't figure out how to do it interactively in c++. I have not been able to find any similar questions anywhere so far.

The closest thing I've come across uses pointers to do something similar to what I want to do, but the book I'm using has not gotten to pointers yet, and I want to try to figure out a way to do it that stays within the bounds of the chapter I'm working in (i.e. not using pointers).

I didn't include the header file or class implementation file because I don't think they are relevant here.

Thank you in advance!

nik
  • 249
  • 1
  • 4
  • 11
  • This is a really interesting question when you can't use pointers! I think it might not be possible to do cleanly without creating a default `Report` and then overwriting it later (which seems silly). – Donald Miner Feb 19 '12 at 03:45
  • You are running into scoping issues - here is a similar question, where someone answers using a pointer: http://stackoverflow.com/questions/1793807/declaring-a-variable-in-an-if-else-block-in-c - For you, you could create a `Report` object outside of the `if` statement, which would invoke the default constructor for the object, and if you get a `name`, then you can reassign it to another `Report`. Of course, if you don't have a default constructor, this solution would not work and the compiler will yell at you. – wkl Feb 19 '12 at 03:47
  • @orangeoctopus: it **is** possible and actually quite trivial. – Dietmar Kühl Feb 19 '12 at 03:59

7 Answers7

16

First off, you cannot create an object within a conditional statement and use it after the conditional statement: the two branches of the conditional statement create a scope each and any object created within in destroyed a the end of the branch. That is, you need to come up with a different approach. The simplest approach is probably to delegate the creation of the object to a function which returns the objects as appropriate:

Report makeReport() {
    if (enter_company_name()) {
        ...
        return Report(name, company);
    }
    return Report();
}

...
Report report = makeReport();

An alternative approach is to use the ternary operator to conditonally create the Report one way or another:

bool get_company_name = enter_company_name();
std::string name(get_company_name? read_name(): "");
std::string company(get_company_name? read_company(): "");
Report report = get_company_name? Report(name, company): Report();

All of these approaches assume that the Report class is actually copyable.

Dietmar Kühl
  • 141,209
  • 12
  • 196
  • 356
  • Thanks. Duh - I should have known that you can't create an object within a conditional and use it elsewhere. For some reason, I guess I thought it was somehow different than other variables. I actually tried your approach of returning an object with a function, and I think that is probably the cleanest and most efficient way to tackle the problem. I failed at this earlier, however, because I didn't realize that objects could be re-initialized, and I couldn't figure out how to create an object while passing user-specified values to the constructor. – nik Feb 19 '12 at 04:19
  • +1d as IMHO better than accepted answer, which requires a default `ctor()` - not something we are always capable or willing to provide. I just ran into a non-default ctor, which needed to be conditionally called with 1 of 2 sets of args, and solved it your way. (Then took it one step further by having it return a member of its temporary... heh) As you said, this requires copyability, but for me, copy is OK but a default `ctor()` isn't. – underscore_d Dec 01 '15 at 22:33
12

I don't know if I understood your question correctly but can't you just declare report before the if/else block and then initialize inside it?

Report header;

if (...) {
  header = Report();
else
  header = Report(name,company);

Or in a shorter way:

Report header; // calls default constructor

if (shouldInitializeWithParams) {
  header = Report(name,company);
}

Of course this requires you to have the empty constructor defined.

Jack
  • 125,196
  • 27
  • 216
  • 324
  • 1
    Wouldn't even need the `header = Report();` line as `Report header;` would invoke the default constructor. – wkl Feb 19 '12 at 03:49
  • Yes, it was for cleariness. Actually the reinitialization is not needed at all, since it would be already initialized. – Jack Feb 19 '12 at 03:51
  • true, and in fact he might just be able to set the `name` and `company` fields on the `header` object rather than construct another `Report` object. – wkl Feb 19 '12 at 03:52
  • Yes, but he seems like he has to use 2 different constructors, if I understood well – Jack Feb 19 '12 at 03:53
  • It's okay; I don't really understand the problem well myself (I kinda suck at OOP), so I probably asked it in a confusing way. I tried that and got two "undefined reference to 'Report::Report()'" errors. Basically, question is can you create objects inside if statements/create them somewhere in code besides declaring at the top of main? Oh, and Jack: yes, the question says to use one constructor under one circumstance, and another under the different set of circumstances. – nik Feb 19 '12 at 03:53
  • From what I understood he is missing the fact that he can declare the object outside the if/else block. Of course you need a common identifier if you need to use it after the block.. but I'm not sure of this :) – Jack Feb 19 '12 at 03:55
  • So if I create one object with the default constructor, how do I then create another one using user-specified values later, with values that have to be input after declaring the objects? – nik Feb 19 '12 at 03:56
  • @nik - The problem your code has is you're declaring a `Report header` object inside the curly braces of your `if`/`else` statements. Those objects have scope (can be used) until the closing `}`, so they go away after the `if` or `else` statement. If you need the object after those statements, you need to declare the variable outside the if statement. – wkl Feb 19 '12 at 03:56
  • @nik you will reinitizalize the object with the new constructor. No memory will be wasted becuase the object, being not a pointer, is just allocated on stack. You will just change its fields.. if you see the code you assign the object you construct to the variable `header` that can be used to reference to the object, either if created with no arguments, either vice-versa with both arguments. – Jack Feb 19 '12 at 03:57
  • birryree: okay, gotcha. Thank you. Jack: okay, sweet. Thank you all! Very helpful. – nik Feb 19 '12 at 03:58
  • @nik - that is what Jack's answer does. – wkl Feb 19 '12 at 03:59
  • @nik Mind that the same answer applied to pointers would be slightly different: you would have to make sure to initialize the object ONLY inside the if branch and the else branch to not leak memory. – Jack Feb 19 '12 at 04:01
  • @Jack Thanks for answering so quickly and thoroughly! I really appreciate it. – nik Feb 19 '12 at 04:07
5

We don't know whether class Report is copy-able, so better to use pointers.

Report * header;

if (...) {
  header = new Report();
else
  header = new Report(name,company);

// after all don't forget
delete header;

and of course you should use header pointer like that

header->print_formatted();
Tim Kachko
  • 2,627
  • 16
  • 21
  • Better? Wouldn't it be better to first at least *check* if Report is copyable? – Benjamin Lindley Feb 19 '12 at 04:00
  • If you can, check it. I can't, sorry :) Does it can nik - I don't know, so better answer is to use pointers, for me, to avoid any problems with copying. – Tim Kachko Feb 19 '12 at 04:03
  • 2
    We do not know whether class `Report` is copyable so better to use a separate function which requires `Report` only to be moveable. Aside if you still recommend pointers use `std::unique_ptr` instead of raw pointers. – Begemoth Feb 19 '12 at 04:14
  • 1
    We should not to assume anything about `Report`. Though I agree about wrapper. – Tim Kachko Feb 19 '12 at 04:19
3

The simplest thing that comes to mind is performing a little refactoring on the code flow. Create a function that processes the input and returns the constructed object:

Report loadReport() {
    if (user_input()) {
        // read input
        return Report(name,company);
    } else {
        return Report();
    }
}

Then call the function from main. The small change in the design is the introduction of a function whose single responsibility is creating a Report from user input, which actually makes sense as a function.

David Rodríguez - dribeas
  • 192,922
  • 20
  • 275
  • 473
2

You can use an rvalue reference to bind in place to either constructed object.

struct Foo
{
  Foo(int bar): bar(bar) {}

  int bar
};

Foo&& f = condition ? Foo(4) : Foo(5);

f.bar = 1000; 
MrBloom
  • 91
  • 6
  • This is cool, but doesn't work with class constructor initializers, where `std::optional` is the more general option. `std::optional` can also result in more readable code, especially when more complex prepwork is needed before calling one of the constructors. – LB-- Sep 10 '20 at 00:10
0

As of C++17, you can now use std::optional for this task - it avoids dynamic memory allocation, it avoids two-phase construction of the object, and it doesn't require the type to be movable or copyable. It allows you to delay the construction of the object while keeping it on the stack and also still being exception-safe. If you construct it in every branch you can safely use it afterward with no performance penalty. It will also work as a class member allowing you to avoid the problem with the class constructor initializer for it, unlike the rvalue reference solution. Demo: https://gcc.godbolt.org/z/vbe5eh

#include <optional>

struct UseCtorA final {};
struct UseCtorB final {};
struct Report final
{
    Report() = delete;
    Report(Report const &) = delete;
    Report(Report &&) = delete;
    Report &operator=(Report const &) = delete;
    Report &operator=(Report &&) = delete;

    Report(UseCtorA, char c) : v{1} { if(c == 't'){ throw 3; } }
    Report(UseCtorB) : v{2} {}

    constexpr auto getValue() const noexcept { return v; }

private:
    int v;
};

int main(int nargs, char const *const *args)
{
    std::optional<Report> report;
    if(nargs > 2)
    {
        report.emplace(UseCtorA{}, args[1][0]);
    }
    else
    {
        report.emplace(UseCtorB{});
    }
    return report->getValue();
}

If you are stuck in an older compiler that only supports C++11, you can make your own really dumb version of std::optional for this express purpose by using a union and placement new:

struct Empty final {};
template<typename T>
struct Optional
{
    Optional() noexcept : unused{} {}
    ~Optional() noexcept(noexcept(v.~T()))
    {
        if(constructed_successfully)
        {
            v.~T();
        }
    }
    template<typename... Args>
    auto emplace(Args &&... args) -> T &
    {
        if(constructed_successfully)
        {
            v.~T();
            constructed_successfully = false;
        }
        T &r = *new (&v) T(std::forward<Args>(args)...);
        constructed_successfully = true;
        return r;
    }
    auto operator->() noexcept -> T *
    {
        return &v;
    }

private:
    union
    {
        T v;
        [[no_unique_address]] Empty unused;
    };
    bool constructed_successfully = false;
};

The generated assembly is identical to with std::optional: https://gcc.godbolt.org/z/vzGz9E

Though, I would recommend using an existing library to supplement your lack of access to std::optional instead of rolling your own like I have done above - my version doesn't work for copying or moving.

LB--
  • 1,898
  • 1
  • 28
  • 72
0

I'm not sure if I understood your question properly. Apologies if you read the answer and realise that that's the case.

But nevertheless, I think the main strategy would be to make use of constructor overloading. ie, you define constructors for both: the case when no parameters are passed and the case when parameters are passed. The former(what you refer to as default constructor) will initialise the company and report names to default values. The latter will assign the parameters received, to the comapany and report names.

As for the use of pointers: you can avoid it by 'declaring' a lot of objects of type 'Report'(class). For eg, you can create an array of header(objects). And then you can 'define' it as and when the user responds.

But by using pointers, you're doing everything in runtime(dynamic assignment) whereas when using arrays(or declaring many objects): the amount is fixed. This can be inefficient.

mehfoos yacoob
  • 358
  • 2
  • 7