7

I'm writing a C++ class for a book that contains a name:

class Book {
private:
    char* nm;
..........
............
..........
...........
};

I am not allowed to use std::string in this assignment. So here I am using strdup to copy the value of the parameter name into nm in the constructor:

Book::Book(const char *name, int thickness, int weight)
    : nm(NULL)
    , thck(thickness)
    , wght(weight)
{
    if (name)
        nm = strdup(name);
}

Is there an alternative of achieving the same result without using strdup, but using the keyword new instead?

aherlambang
  • 14,290
  • 50
  • 150
  • 253
  • 2
    Usually you would use `std::string` but what exactly do you mean by "without using the C++ STL library"? i.e. which parts of the standard library are you trying to avoid (and why)? – CB Bailey Mar 14 '10 at 22:38
  • Why can't you just use `strdup`? You're asking for a tool to do something while refusing to use the ideal one. – Ben S Mar 14 '10 at 22:38
  • it's a constraint on the assignment... maybe I should add a homework tag there... and yes by STL I mean using string – aherlambang Mar 14 '10 at 22:40
  • 1
    probably there should be another tag `not-a-real-problem` ... – stefanB Mar 14 '10 at 22:44
  • This assignment must have first been conceived before strdup was part of the standard library. Surely the answer to "How would you duplicate a string without *strdup*?" should be " **Why** would I?" :) – Duncan Mar 14 '10 at 22:58
  • @stefan: I agree it's not a real problem per se, but Alex can't help the restrictions he as, so we may as well treat it like a real problem. – GManNickG Mar 14 '10 at 23:10
  • @Duncan: I doubt it. Maybe a more sensible stand-alone assignment would be, "(1) Write an implementation of strdup. (2) Modify it to allocate memory with `new` instead of `malloc`". But I reckon this isn't stand-alone, it's one part of a larger assignment intended to teach several things. Hopefully the denouement will be, "now you've learned how to handle resources, learn that you almost always don't have to: std::string!". – Steve Jessop Mar 14 '10 at 23:46
  • @Steve Jessop: That's certainly better worded. It puts the focus on understanding implementation rather than trying to carve circular things out of square blocks of wood to make your wagon roll smoothly. Back in the day, one of my assignments included learning how to copy a directory with `(cd src-parent && tar cf - src) | (cd dst-parent tar xf -)` for no other reason than `cp` had no recursive switch. I thought maybe the professor may just be recycling his note from a similar circumstance :) – Duncan Mar 15 '10 at 00:35
  • @Gman: that's why I provided proper answer before leaving a comment ... – stefanB Mar 15 '10 at 00:37
  • @Duncan: or use `rsync` ;-) (with or without `--delete`: not sure what `cp -r` does when it does exist). – Steve Jessop Mar 15 '10 at 00:41
  • @Duncan: `strdup` isn't and hasn't ever been part of the standard library (C or C++). – CB Bailey Mar 15 '10 at 06:22

5 Answers5

5

Strictly speaking: The string class is part of the Strings library. This is much easier to use, dynamic in nature and you have less worry when copying/assigning than C-style strings.

The other approach is to manually copy out:

class Book {
   public:
     Book(const char *name, ...) : nm(0), ... {
           if (!name) throw "invalid parameter";
           nm = new char [ strlen(name) + 1 ];
           strcpy(nm, name);
     }
     ~Book() {
           delete [] nm;
           // ...
     }
     Book(Book const& o) : nm(0), ... {
           if (!name) throw "invalid parameter";
           char *p = new char [ strlen(name) + 1 ];
           if (p) {
               strcpy(p, name);
               delete [] nm;
               nm = p; 
           }
     }
     Book& operator=(Book const& o) {
           if (this != &o) {
              char *p = new char [ strlen(name) + 1 ];
              if (p) {
               strcpy(p, name);
               delete [] nm;
               nm = p; 
              }
           }
           return *this;             
     }
 };

The problem with this approach is that you will have to manage the memory yourself and implement all the Big-three special member functions yourself (and ensure exception-safety as much as you can).

dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • You don't have to check `if (p)` after `new`, because only nothrow new can return a null pointer. You do have to free the old array in `operator=`. Copy-and-swap is usually better. – Steve Jessop Mar 14 '10 at 23:03
  • Yes, the second was a typo. The former I wanted to put in as a comment. (I assumed that the OP is working in a restricted environment and may not have access to exception handling.) – dirkgently Mar 14 '10 at 23:11
  • I think that delete p is meant to be delete [] nm. Also, how about a set_string function instead of copy and paste code. – tony Mar 14 '10 at 23:14
  • and once you have set_string, you might decide to reuse nm if it is big enough for the next string, etc (instead of allocating a new one and throwing away the old) – tony Mar 14 '10 at 23:15
  • and then eventually realizing you need a string class, and writing one..... oh wait... – tony Mar 14 '10 at 23:15
  • Thanks tony. Sure! When not coding on an IDE and without my first cup of coffee is taking its toll on me :( – dirkgently Mar 14 '10 at 23:16
  • well I guess Alexander will not learn anything today because you gave him code instead of describing the steps - good for us, less competition on the market ... – stefanB Mar 15 '10 at 00:36
4

Not really an answer, but a correction to dirkgently's that won't fit in a comment: you really shouldn't write so much code as he did.

Safe object copying isn't something you want to get too badly wrong, although in real life the best way to avoid that is of course to use the appropriate library classes in the first place. That said, a simple C-style string is as good an example as anything else to practice with:

class Book {
    char *nm;
public:
    Book(const char *name) : nm(copystr(name)) { /* don't throw an exception! */ }
    Book(const Book &o) : nm(copystr(o.nm)) { /* Likewise! */ }
    ~Book() { delete[] nm; }
    Book& operator=(const Book &o) {
       // this is called copy-and-swap (CAS). If you absolutely
       // have to write this kind of resource-managing code, then
       // you will need this technique, because it's the best
       // way to provide the strong exception guarantee.
       Book cp = o;
       swap(cp);
       return *this;
    }
    /* or you can do this:
    Book& operator=(Book cp) {
       swap(cp);
       return *this;
    }
    */
    void swap(Book &o) {
       std::swap(this->nm, o.nm);
       // also swap other members
    }
};

char *copystr(const char *name) {
    if (!name) return 0;
    char *newname = new char[strlen(name)+1];
    std::strcpy(newname, name);
    return newname;
}

See the "don't throw an exception!" warning in the constructor? That's because if you do, the string will be leaked. If you need more than one resource in your class that requires explicit freeing, that's when things become really tedious. The right thing to do is to write a class just for the purpose of holding the string, and another one for the purpose of holding the other resource, and have one member of each type in your Book class. Then you don't have to worry about exceptions in the constructor, because members which have been constructed are destructed if the constructor body of the containing class throws. Once you've done this a couple of times, you'll be pretty keen to use the standard libraries and TR1.

Normally, to save effort you'd start by making your class non-copyable, and only implement the copy constructor and operator= if it turns out you need them:

class Book {
    char *nm;
public:
    Book(const char *name) : nm(copystr(name)) { }
    ~Book() { delete[] nm; }
private:
    Book(const Book &o);
    Book& operator=(const Book &o);
};

Anyway, strdup is no great mystery. Here are a couple of very similar implementations (both from GNU), just by searching for "strdup.c". The same approach usually works for other string-handling functions, and in general anything that doesn't require special platform-dependent mechanisms to implement: look for "function_name.c" and you'll probably find a GNU implementation that explains how it's done, and how you can do similar but different things. In this case you'd start with their code and replace the call to malloc and the error-handling.

http://www.koders.com/c/fidF16762E3999BA95A0B5D87AECB0525BA67CEE45A.aspx

http://cvs.frodo.looijaard.name/viewvc/cgi-bin/viewvc.cgi/public/psiconv/compat/strdup.c?revision=1.1.1.1&view=markup

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
3

Yes there is an alternative.

  • get a size of string
  • create an array of the same size as is the string
  • copy the contents of the string into that array
  • point nm to your allocated array

Or you could use strdup - btw strdup is not part of C++ STL.

stefanB
  • 77,323
  • 27
  • 116
  • 141
0

You have to malloc with strlen and then use strcopy. Stupid homework btw.

ypnos
  • 50,202
  • 14
  • 95
  • 141
0
Book::Book(const char *name, int thickness, int weight):nm(NULL), thck(thickness), wght(weight){ 
  if (name) {
     size_t length = strlen(name);
     nm = new char[length + 1];
     memcpy(nm, name, length + 1);
  }
JBRWilkinson
  • 4,821
  • 1
  • 24
  • 36