开发者

C++ advice on writing code

开发者 https://www.devze.com 2023-01-13 21:12 出处:网络
I am having difficulty writing my code in the way it should be written. This is my default constructor:

I am having difficulty writing my code in the way it should be written. This is my default constructor:

Address::Address() : m_city(NULL), m_street(NULL), m_buildingNumber(0), m_apartmentNumber(0)
{}

...and this is my other constructor:

Address::Address(const char* city, const char* street, const int buildingNumber,const int apartmentNumber) : m_city(NULL), m_street(NULL)
{
    SetAddress(city,street,buildingNumber,apartmentNumber);
}

I have to initialize my city and street fields as they contain char * and my setter uses remove to set a new city for example. I would very much like to hear your opinion on how to write it in the right way without repeating code. this is my SetAddress code :

bool Address::SetAddress(const char* city, const char* street, const int buildingNumber, const int apartmentNumber)
{
    if (SetCity(city) == false || SetStreet(street) == false || SetBuildingNumber(buildingNumber) == false || SetApartmentNumber(apartmentNumber) == false)
        return false;
    return true;
}

and this is my SetCity:

bool Address::SetCity(const char* city)
{
开发者_StackOverflow社区    if(city == NULL)
        return false;
    delete[] m_city;
    m_city = new char[strlen(city)+1];
    strcpy(m_city, city);
    return true;
}

1 more question if i do change char* to string how can i check if string city doesnt equal to NULL as i know string does not have the "==" operator and string is an object and cannot be equal to null, how can i check if the string i get is indeed legeal.


You should use std::string instead of C strings (const char*). Then you don't have to worry about having a "remove" function because std::string will manage the memory for you.


The only repeating code I see is the initializers. Since you should both be using initializers and cannot share initializers, some code redundancy is required here. I wouldn't worry about it.

When the new C++ comes out you'll be able to call the former constructor during initialization of the later. Until then, you'll just have to live with this minor smell.


You can combine the two ctors:

Address::Address(const char* city=NULL, 
                 const char* street=NULL, 
                 int buildingNumber=0,
                 int apartmentNumber=0) 
: m_city(city), 
  m_street(street), 
  m_buildingNumber(buildingNumber), 
  m_apartmentNumber(apartmentNumber)
 {}

[The top-level const on buildingNumber and apartmentNumber accomplished nothing and attempt to move implementation information into the interface, so I remove them.]

Of, if you really prefer:

Address::Address(const char* city=NULL, 
                 const char* street=NULL, 
                 int buildingNumber=0,
                 int apartmentNumber=0) 
{
    SetAddress(city,street,buildingNumber,apartmentNumber);
}

I generally prefer the former, but if SetAddress qualifies its inputs, it may be worthwhile. Of course, the suggestion to use std::string instead of pointers to char is a good one as well, but that's a more or less separate subject.

One other minor note: this does differ in one fundamental way from your original code. Your code required either 0 or 4 arguments to the ctor. This will accept anywhere from 0 to 4, arguments so a person could specify (for example) a city and street, but not a building number or apartment number. If it's really important to you that attempts at using 1, 2 or 3 arguments be rejected, this approach won't be useful to you. In this case, the extra flexibility looks like an improvement to me though -- for example, if somebody lives in a single-family dwelling, it's quite reasonable to omit an apartment number.


As answered by others (James McNellis' answer comes to mind), you should switch to std:string instead of char *.

Your problem is that repetition can't be avoided (both non default constructor and the setAddress method set the data), and having one calling the other could be less effective.

Now, the real problem, I guess, is that your code is doing a lot, which means that repetition of delicate code could be dangerous and buggy, thus your need to have one function call the other. This need can be remove by using the std::string, as it will remove the delicate code from your code altogether.

As it was not shown Let's re-imagine your class:

class Address
{
    public :
        Address() ;
        Address(const std::string & p_city
              , const std::string & p_street
              , int p_buildingNumber
              , int p_apartmentNumber) ;
        // Etc.

    private :
        std::string    m_city ;
        std::string    m_street ;
        int            m_buildingNumber ;
        int            m_apartmentNumber ;
} ;

Using the std::string instead of the const char * will make the std::string object responsible for handling the resource (the string itself).

For example, you'll see I wrote no destructor in the class above. This is not an error, as without a destructor, the compiler will generate its own default one, which will handle the destructor of each member variable as needed. The remove you use for resource disposal (freeing the unused char *) is useless, too, so it won't be written. This means a lot of delicate code that won't be written, and thus, won't produce bugs.

And it simplifies greatly the implementation of the constructors, or even the setAddress method :

Address::Address()
    // std::string are initialized by default to an empty string ""
    // so no need to mention them in the initializer list
    : m_buildingNumber(0)
    , m_apartmentNumber(0)
{
}

Address::Address(const std::string & p_city
               , const std::string & p_street
               , int p_buildingNumber
               , int p_apartmentNumber)
    : m_city(p_city)
    , m_street(p_street)
    , m_buildingNumber(p_buildingNumber)
    , m_apartmentNumber(p_apartmentNumber)
{
}

void Address::setAddress(const std::string & p_city
                       , const std::string & p_street
                       , int p_buildingNumber
                       , int p_apartmentNumber)
{
    m_city             = p_city ;
    m_street           = p_street ;
    m_buildingNumber   = p_buildingNumber ;
    m_apartmentNumber  = p_apartmentNumber ;
}

Still, there is repetition in this code, and indeed, we'll have to wait C++0x to have less repetition. But at least, the repetition is trivial, and easy to follow: No dangerous and delicate code, everything is simple to write and read. Which makes your code more robust than the char * version.


Your code looks good - it might be worthy to see the contents of SetAddress. I would highly recommend using std::string over char *s, if city and street aren't hard-coded into the program, which I doubt. You'll find std::string will save you headaches with memory-management and bugs, and will generally make dealing with strings much easier.


I might rewrite the setAddress() method as follows:

bool Address::setAddress(const char* city, const char* street, const int buildingNumber,    const int apartmentNumber)
{
    return (setCity(city)
      && setStreet(street)
      && setBuildingNumber(buildingNumber) 
      && setApartmentNumber(apartmentNumber))
}

which will achieve the same short-circuiting and returning semantics, with a bit less code.


If you must use char * rather than std::string you need to manage the memory for the strings yourself. This includes copy on write when sharing the text or complete copy of the text.

Here is an example:

class Address
{
public:
    Address(); // Empty constructor.
    Address(const char * city,
            const char * street,
            const char * apt);  // Full constructor.
    Address(const Address& addr);  // Copy constructor
    virtual ~Address();  // Destructor
    void set_city(const char * new_city);
    void set_street(const char * new_street);
    void set_apartment(const char * new_apartment);
private:
    const char * m_city;
    const char * m_street;
    const char * m_apt;
};


Address::Address()
    : m_city(0), m_street(0), m_apt(0)
{ ; }


Address::Address(const char * city,
                 const char * street,
                 const char * apt)
    : m_city(0), m_street(0), m_apt(0)
{
    set_city(city);
    set_street(street);
    set_apt(apt);
}


Address::Address(const Address& addr)
    : m_city(0), m_street(0), m_apt(0)
{
    set_city(addr.city);
    set_street(addr.street);
    set_apt(addr.apt);
}


Address::~Address()
{
    delete [] m_city;
    delete [] m_street;
    delete [] m_apt;
}


void Address::set_city(const char * new_city)
{
    delete [] m_city;
    m_city = NULL;
    if (new_city)
    {
        const size_t length = strlen(new_city);
        m_city = new char [length + 1]; // +1 for the '\0' terminator.
        strcpy(m_city, new_city);
        m_city[length] = '\0';
    }
    return;
}


void Address::set_street(const char * new_street)
{
    delete [] m_street;
    m_street = NULL;
    if (new_street)
    {
        const size_t length = strlen(new_street);
        m_street = new char [length + 1]; // +1 for the '\0' terminator.
        strcpy(m_street, new_street);
        m_street[length] = '\0';
    }
    return;
}


void Address::set_apt(const char * new_apt)
{
    delete [] m_apt;
    m_apt = NULL;
    if (new_apt)
    {
        const size_t length = strlen(new_apt);
        m_apt = new char [length + 1]; // +1 for the '\0' terminator.
        strcpy(m_apt, new_apt);
        m_apt[length] = '\0';
    }
    return;
}

In the above example, the Address instance holds copies of the given text. This prevents problems when another entity points to the same text, and modifies the text. Another common issue is when the other entity deletes the memory area. The instance still holds the pointer, but the target area is invalid.

These issues are avoided by using the std::string class. The code is much smaller and easier to maintain. Look at the above code versus some of the other answers using std::string.

0

精彩评论

暂无评论...
验证码 换一张
取 消