JMoore
JMoore

Reputation: 95

Calling the default constructor from another constructor

I am trying to make the overloaded constructor call the default constructor, but it only gives me garbage numbers. What I want it to do is recognize if the date entered is invalid, and so default it to 1/1/2000.

#include <iostream>
#include <iomanip>
#include <string>
#include "date.h"

using namespace std;

Date::Date () 
{
        month = 1;
        day = 1;
        year = 2000;
        monthName = "Jan ";
        format = 'D';
        valid = true;
}

Date::Date (int m, int d, int y)
{   
    valid = false;

        if (y > 0)
        {
            //January
            if (m == 1 && d >= 1 && d <= 31)
            {
                month = m; day = d; year = y; 
                monthName = "Jan "; valid = true;
            }

            //February
            else if (m == 2 && d >= 1 && d <= 28)
            {
                month = m; day = d; year = y; 
                monthName = "Feb "; valid = true;
            }

            //etc.
        }

    if (valid == false)
        Date ();
}

Upvotes: 0

Views: 192

Answers (2)

Drew Dormann
Drew Dormann

Reputation: 63755

A constructor can only be called when an object is first being constructed. It is not a general-purpose function.

Your code here is doing that - constructing a new Date, but not doing anything with it.

Date ();

You can achieve what you want by assigning that new, default-constructed Date.

*this = Date(); 

Edit: Be sure that valid is what you want it to be in this case.

Upvotes: 1

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145239

First, in

Date ();

you are constructing a temporary, and discarding it. C++ does have low level facilities to call a constructor on existing storage, but an ordinary constructor call just creates a new object.

Also note that

if (valid == false)

can and should be more cleanly expressed as just

if( not valid )

or if you like symbolic operators,

if( !valid )

Now, the intent of the original code can be expressed

  • by forwarding to a common constructor (a natural way would be by invoking and passing the result of a month name function), or

  • by default-constructing first and then modifying, or

  • by assigning a default-constructed instance.

These are in order of most clean to most unclean.

Do note that assigning a default-constructed instance, the most dirty option above, and doing nothing more, as suggested in another answer, will set the valid member to true, thus removing all information about the fact that the constructor arguments were invalid…

However, none of these options are good! For the intent, that of treating an argument error as a request for a default, is itself very ungood. Instead, when you detect an argument error, throw an exception or terminate, so that the client code will not have on hand a possibly unexpected object.

E.g., do

if( not valid ) { throw std::runtime_error( "Date::<init>: invalid args" ); }

Some folks prefer to use std::logic_error or std::range_error.

In passing, with Visual C++ force-include <iso646.h> to get support for the C++ keywords (less imprecisely, the reserved words) and, or and not.


Example of (not recommended! but least dirty of original intent implementations) common constructor approach:

class Date
{
private:
    int     day_;
    int     month_;
    int     year_;
    string  month_name_;
    bool    is_valid_;

    Date( int month, int day, int year, const string& month_name );

public:
    static
    auto month_name_for( int month, int day, int year )
        -> string;

    Date();
    Date( int month, int day, int year );
};

Date::Date( const int m, const int d, const int y, const string& month_name )
    : month_(         month_name == ""? 1     : m )
    , day_(           month_name == ""? 1     : d )
    , year_(          month_name == ""? 2000  : y )
    , month_name_(    month_name == ""? "Jan" : month_name )
    , is_valid_( month_name != "" )
{}

auto Date::month_name_for( const int m, const int d, const int y )
    -> string
{
    if( y > 0 )
    {
        if( m == 1 && 1 <= d && d <= 31 )           { return "Jan "; }
        const int days_in_feb = 28;     // TODO: correct for leap year
        if( m == 2 && 1 <= d && d <= days_in_feb )  { return "Feb "; }
        if( m == 3 && 1 <= d && d <= 31 )           { return "Mar "; }
        //etc.
    }
    return "";
}

Date::Date ()
    : Date( 0, 0, 0, "" )
{}

Date::Date( const int m, const int d, const int y )
    : Date( m, d, y, month_name_for( m, d, y ) )
{}

Example of each constructor ensuring a valid object (recommended):

class Date
{
private:
    int     day_;
    int     month_;
    int     year_;

public:
    static
    auto month_name_for( int month )
        -> string;
    static
    auto is_valid( int month, int day, int year )
        -> bool;

    Date();
    Date( int month, int day, int year );
};

auto Date::month_name_for( const int m )
    -> string
{
    static const string names[] = { "Jan", "Feb" };        // Etc.
    return (1 <= m && m <= 12? names[m-1] : "");
}

auto Date::is_valid( const int m, const int d, const int y )
    -> bool
{
    if( y > 0 )
    {
        if( m == 1 && 1 <= d && d <= 31 )           { return true; }
        const int days_in_feb = 28;     // TODO: correct for leap year
        if( m == 2 && 1 <= d && d <= days_in_feb )  { return true; }
        if( m == 3 && 1 <= d && d <= 31 )           { return true; }
        //etc.
    }
    return false;
}

Date::Date ()
    : Date( 1, 1, 2000 )
{}

Date::Date( const int m, const int d, const int y )
    : month_( m ), day_( d ), year_( y )
{
    if( not is_valid( m, d, y ) )
    {
        throw runtime_error( "Date::<init>: invalid arguments" );
    }
}

Upvotes: 1

Related Questions