5

To continue the discussion in Why are pointers not recommended when coding with C++?

Suppose you have a class that encapsulates objects which need some initialisation to be valid - like a network socket.

// Blah manages some data and transmits it over a socket
class socket; // forward declaration, so nice weak linkage.      

class blah
{
  ... stuff 
  TcpSocket *socket;
}

~blah {
   // TcpSocket dtor handles disconnect
   delete socket; // or better, wrap it in a smart pointer
}

The ctor ensures that socket is marked NULL, then later in the code when I have the information to initialise the object.

// initialising blah
if ( !socket ) {
   // I know socket hasn't been created/connected
   // create it in a known initialised state and handle any errors 
   // RAII is a good thing ! 
   socket = new TcpSocket(ip,port);
}

// and when i actually need to use it
if (socket) {
   // if socket exists then it must be connected and valid 
}

This seems better than having the socket on the stack, having it created in some 'pending' state at program start and then having to continually check some isOK() or isConnected() function before every use.
Additionally if TcpSocket ctor throws an exception it's a lot easier to handle at the point a Tcp connection is made rather than at program start.

Obviously the socket is just an example, but I'm having a hard time thinking of when an encapsulated object with any sort of internal state shouldn't be created and initialised with new.

4
  • You'll need to either disallow copying, or implement copy/move semantics also in order to avoid double delete. You know, the Rule of 3. Sep 2, 2012 at 19:53
  • 2
    Also, I'd add that it's not necessarily the case that pointers should NEVER be used - just rarely. Particularly, pointers should be avoided as much as possible especially in application code. But in library code, like your socket class, pointers are more likely to have valid uses because the resource management is encapsulated in the class. Sep 2, 2012 at 19:57
  • @CharlesSalvia - yes, I dissallow copying for all high level class. Raw data pointers are generally better with stl or mmap() - but for encapsulation I think pointers are best. Sep 2, 2012 at 19:57
  • 1
    note that your 'blah' class is effectively (almost) a smart pointer. Nothing wrong with using a pointer in such a way, the problems come when you're passing them around like biscuits.
    – gbjbaanb
    May 8, 2014 at 8:14

5 Answers 5

19

In general, programming constructs and techniques are commonly considered to be 'bad' when there are 'better' alternatives available for a particular task. The use of a pointer may be technically correct in a lot of places, but it's rare in C++ for a situation to arise where the use of a raw pointer doesn't have a better alternative.

Most of the time, using references, smart pointers, iterators and standard library containers will result in safer/cleaner/more idiomatic code when compared to an equivalent solution using pointers; and usually at no extra cost to the programmer (quite frequently at a lower cost in fact).

There will always be occasions when a raw pointer is the most sensible option, and in those cases nobody gains by trying to find "clever" ways of avoiding pointers; particularly if avoiding a raw pointer means risking larger breaking changes to working legacy code which may otherwise have not needed to change; but for new code at least these situations are unusual for anybody using a modern C++11 implementation.

3
  • 2
    +1 Great answer, short, to the point and makes the distinction between legacy and modern C++.
    – mattnz
    Sep 2, 2012 at 21:46
  • 1
    So this model wrapped with a smart pointer would be the ideal solution? Sep 3, 2012 at 2:18
  • 2
    @MartinBeckett IMO, yes, it would. You get all the benefits of raw pointers without the cost (remembering to delete) or the risks (memory leaks). May 8, 2014 at 7:57
2

Pointers do have good reasons to be used too, as you argue. Though, in most cases you should get away with the standardized smart pointers introduced in C++11 to gain additional safety, while retaining the flexibility pointers give to you.

Though, it's also true that they might seem confusing to people who aren't so familiar with C++, so from maintenance perspective it'd be perhaps best to rely on the most simplest to understand techniques and methodologies to implement things.

2
  • The nice thing about pointers is that null is universal. Otherwise every object has a confusing set of isOK(), isValid(), isInit() which mean different things Sep 2, 2012 at 16:57
  • 2
    smart pointers all implement operator bool which is even better since you don't have to compare against some magic constant like NULL
    – stijn
    Sep 2, 2012 at 17:12
1

In my opinion the pointers are described as difficult to handle or bad because they break 2 important aspects of any application design:

  • semantics
  • syntax

if you remember, a basic concept in the programming world is given by the fact that if I wrote 4 on a blackboard, this means nothing until i give a context and a semantic to what i have wrote on the blackboard, for example if after i have wrote 45 i say that what is on the blackboard it's my age, you can get 45 as a real information, but without a context/semantics that 45 is useless and without a clear interpretation: the real information and its representation are different.

This is even clearer in technology like OpenGL, you can do almost nothing with your pointer if you don't supply a context, the same thing happens in C++, if you don't provide a context ( a target type ) for a pointer or a reference, the pointer alone means nothing and it's useless.

The syntax is also important because it set boundaries about the business logic of your application and the lifetime of your components, pointers can potentially break all the boundaries and your business logic, never the less, pointers contains references so they do not even have a real state, they just point to something in memory that they can't handle during the lifetime of your application, they are more like post-it with a memory address rather than objects with a well defined state and a well defined lifetime.

All the major difference between the old C++ and C++11 are differences about design, which means syntax and semantics that can drive the logic of your application; the smart pointers are nothing more than pointers but with a big plus related to a specific design that comes within the pointer itself, so you can have more control over it.

In the end the pointers can't have a state and need a context to be handled, this is something that a safe and good application design can't usually accept, the smart pointers are a step in the design oriented world which is basically the modern approach to the programming world.

4
  • 1
    true if you are passing void* around - but a Tcpsocket* has full context and state Sep 2, 2012 at 17:11
  • 2
    @MartinBeckett a good designer doesn't accept an if, you can't say "i have done this beautiful design but you have to use it as i said" this will not be a good design, period. A good design has to drive the application on its own.
    – user827992
    Sep 2, 2012 at 17:13
  • @user827992 You can say that. It's called a contract. If you break it bad things will probably happen and it'll be your fault, not the designers.
    – James
    Sep 2, 2012 at 23:55
  • 1
    @James: "it'll be your fault, not the designers" ... Good contract must result from a negotiation of both the parties. Breaking a "bad written" contract result in "bad" things, but is is much more a designer's fault, than user's fault. Remeber that design is clueless if no-one is able to use it. Good contracts inhibit improper use, don't punish after allow it. Punishment can make the designer satisfied, but if diffused has a "social cost" the designer also will pay. Do you get the metaphore? Sep 6, 2012 at 14:59
1

The pointer is definitely not a bad thing, it gives you direct access to the memory, for the best performance.
Sometimes it is difficult to track the lifetime, when the pointer needs to be passed around classes, this is where the smart pointer can help. If you can sacrifice the little performance impact, then yes, use smart pointer if it is available.

The bad thing about using smart pointer is the dependency of the header file for the class. With raw pointer, you can just forward declare the class/struct, and have a cleaner dependency during compilation.
You must also ensure the smart pointer you use is thread-safe in case of multi-threading application.

If your class is simple enough, I would say stick with regular pointer than a smart one.

0

Pointers are bad for the following reasons:

  1. Information about memory areas disappears. It is no longer clear if your TcpSocket memory is inside the blah object or outside of it. References and composition can fix it. (reference=outside, composition=inside)
  2. Pointer can be NULL. How well you expect the program to work if the function call behaviour is not respected. If you call connect() function, it better do the connection, but failing simply because some ptr is NULL is not good behaviour -- failures should have real reasons like network disconnected. Failing simply because some previous call failed is not good behaviour.
  3. Dynamic behaviour like the ptr==NULL should be done with booleans and not this kind of hidden behaviour where ptr==NULL. Then it would be easier to figure out the reason for the failure when booleans have proper names independent of the pointer.
  4. Best choice for failures is that they just never happen.
  5. 2nd best choice is that part of the application is disabled when network is not available - like no new information appears, and connection is restored as soon as possible.
  6. If your socket functions can fail, the function prototype better have that information. Your void connect() is not acceptable, int connect() with error code is better. Returning some object which contains fail bit is also ok.

Example of what kind of alternatives are available: (the composition one)

class Blah {
public:
  Blah(ip,port) : s(ip,port) { }
private:
  TcpSocket s;
  bool failbit;
};

Or with references:

class Blah {
public:
  Blah(TcpSocket &s) : s(s) { }
private:
  TcpSocket &s;
};

If the connection has to be created afterwards, like address not known early enough in constructor, then use this:

class Blah {
public:
   Blah() : sock(NULL) { }
   TcpSocket &connect(ip, port) {
     delete sock;
     sock = new TcpSocket(ip,port);
     return *sock;
    }
  ~Blah() { delete sock; }
private:
  TcpSocket *sock;
};

But this is usually not the best alternative... (call connect twice and previous one loses connection -- the returned TcpSocket must not be stored anywhere) The next alternative will need std::vector < TcpSocket*>...

class Blah {
public:
   Blah() : vec(), error("logfile.txt") { }
   int connect(ip,port) {
      vec.push_back(new TcpSocket(ip,port));
      return vec.size()-1;
   }
   int write(int conn, char *data) {
      if (vec[conn])
         { vec[conn]->write(data); return OK; }
      else return ERR;
   }
  TcpSocket &connection(int conn) const 
      { if (vec[conn]) return *vec[conn]; else return error; }
  void disconnect(int conn) { delete vec[conn]; vec[conn]=NULL; }
   ~Blah() { for(int i=0;i<vec.size();i++) delete vec[i]; }
private:
   std::vector<TcpSocket*> vec;
   TcpSocket error;
};

Note that returning index to the connection std::vector prevents removing connections (indexes will get broken/have to make them NULL). The last one just have a problem that connect() return value and write() return value is different. Should use some kind of type checking trick to make them different ints:

struct Err { int error; };
Err write(int conn, char *data);
5
  • 1) The question is clearly about initialisation. Anyone who passes raw pointers through an API deserves what's coming to them. 2) I fail to see the point here. He could always do if (!socket) return E_NOTCONNECTED 3) Says who? Some coding standard explicitly require checking against NULL 4) Not really a point. Failures do happen. 5) Not relevent to the question 6) void connect() could be perfectly acceptable if it throws if things aren't ok. Returning an object on the stack seems like an awful idea
    – James
    Sep 2, 2012 at 19:24
  • who is passing raw pointers through an api?
    – tp1
    Sep 2, 2012 at 19:25
  • I assumed this is what you were talking about in 1)? No?
    – James
    Sep 2, 2012 at 19:29
  • The second 'solution' of using references merely passes the problem on to the class that creates an instance of Blah
    – James
    Sep 2, 2012 at 19:44
  • This still forces you to create the socket before you have the information necessary (eg a dialog asking for the address) - so you need to add some isOk() type check to every use Sep 2, 2012 at 19:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.