57

This question may sound strange to you, but I am learning C++ all by myself. I have nobody whom I could ask for mentoring and I would be very glad for some advice.

I have started recently to program in C++ (about 3 - 4 intensive months with about 6 - 8 daily hours). My background is Java and I have done some bigger projects in Java with over 10k LOC (which is for a university student like me big).

I used C++ mainly for algorithms implementing and visualization but I aim for bigger software projects as well. The only libraries I used are for Catch2, OpenCV, and a little Boost. The strange thing about my programming style is that I have never used pointers in my journey; it is not like I don't know how to use pointers but I just never found a moment where I think a pointer would be useful. When I have to store primitive data, I prefer std::vector over array. When I need to use an object of a class, I prefer to create the object on the stack and pass it by reference; no new/delete, no smart pointers.

The reason why I ask this (strange) question is, that I feel like I am missing a big area of C++ programming. Could you share with me your experience and maybe give me some tips?

  • 5
    How do you handle polymorphism? – Jeffrey May 25 at 17:57
  • 128
    You're missing out on all the memory safety bugs, most segfaults, invalid casts, dangling pointer errors, leaks, ... a whole lot of undefined behavior is out of your reach :-) – Mat May 25 at 18:43
  • 3
    @Mat with smart pointer you avoid a lot of this mess as well. And what aboutnpolymorphism and abstract types in containers without pointers ? – Christophe May 25 at 20:07
  • 4
    The examples you quoted don't need pointers at all, so you're doing fine. I guess you you haven't find a reason to use pointers (or better smart pointers) yet is, how you stated, because you mainly implement algorithms in a strict range so you don't need to use much polymorphism. Anyway, still weird if you're using OpenCV, this means you never needed to access the underlying buffer of an image, or acquire from a camera or convert to another image format (eg QImage for Qt). – Moia May 26 at 7:07
  • 4
    In my experience, for purely algorythmic problems it's quite common that pointers are not necessary, and using vectors and indices is more natural. The pointers become more natural when you use OOP, but for pure algorithms OOP is usually just a nuisance. – Frax May 26 at 14:22

11 Answers 11

56

It may be a good idea to avoid pointers, using object copies and references whenever possible, like you do. Continue this practice.

However there are a certain number of things that can go awfully wrong, if you're not extremely careful:

  • Objects "on the stack" (the C++ terminology nowadays for this storage class is "auto") must stay valid when you use their reference. This works generally well if you pass a reference to a function. But returning a reference back is doomed to fail: the object will be destroyed immediately after the return and using the reference is then UB. The same kind of issues occur when you to inject a reference into an object: it's a ticking bomb.
  • You cannot use polymorphism with containers. So if you never use pointers, but have vector of classes with virtual function members, your code might not work as you think because of slicing. These are extremely nasty bugs and are a common mistake when new to C++ with a java background.

There are also some very common OO design patterns that are not possible without pointers, such as the factory method pattern.

Avoiding pointers should not be an end per se. If you're working with visualisation, I guess that polymorphism may be your friend. And here pointers can unlock the situation. The good news is that smart pointers nowadays can safely manage the memory for you.

So yes, your practice may very well work. But it might contain some unnoticed bugs. And sooner or later you'll miss very useful features.

| improve this answer | |
  • 13
    std::vector<std::variant<...>> will get you pretty far as a polymorphic container. It's a different kind of polymorphism than inheritance-based (with an emphasis on a closed set of types instead of a closed set of functionality), but it will still cover a lot of use cases. – chris May 26 at 4:02
  • @chris indeed you can do a lot with std::variant and even more in combination with std::visit. But as you rightly pointed out, this implies a closed set of types, which seems not so in line of the open/closed principle: I just like the simplicity of polymorphism when working with Shapes without worrying about limiting the set of possibilities and having later to modify existing code to add that Heptagon nobody was mentioning initially ;-) – Christophe May 26 at 7:17
  • 7
    I would also mention that many things Java programmers do with polymorphism can actually better be done with templates in C++. Specifically, CRTP is essentially covariant polymorphism that's completely resolved at compile time. — Of course, when you really want a heterogeneous container then unique_ptr or variant is the way to go, depending on whether you want to limit the possible different options or not. – leftaroundabout May 26 at 11:54
  • @leftaroundabout indeed, you’re perfectly right: templates offer a great deal of compile-time flexibility. Nevertheless, java doesn’t force you as far as I know to choose between compile-time or run-time polymorphism. In this regard, the closest thing to a java object reference in C++ seems to be the shared_ptr (see the code example linked behind « extremely nasty bug »). And there are a couple of problems that really need run-time flexibility that are easier expressed with inheritance than with templates (without disregarding the latter either). – Christophe May 26 at 13:34
  • 1
    @NoSenseEtAl I meant that we should first things first, and look at the semantics we have to implement before choosing the tool. If I have an application that manages hetregeneous containers of Shape, I should use inheritance for specialization and not try to use a variant<Square,Circle,Rectangle,....>. On the other side, if I’d have to write an application that deals with containers of Robots,Colors and Flowers, I should use a variant, since these concepts have nothing in common and I shouldn’t try to invent a common concept when it doesn’t exist ;-) – Christophe May 27 at 16:37
19

You are using "indirect reference in the general sense" in your C++ programming. There's nothing wrong with that. It is almost as powerful as programming with pointers and reference (in the C++ language).


Although, there is one technicality pitfall in the way you use vector.

For software safety sake, I'll describe that technical pitfall first. It's more important to know about that pitfall, than to have your main question answered.

When objects are placed into a vector, the vector contains a copy of the object. These objects have addresses, from which you can create references (in the C++ sense). These addresses are stable until the next time you cause the vector to reallocate (growing, clearing), or causing the items to shift (e.g. delete an item in the middle). If you keep using addresses that are no longer valid (called pointer invalidation), it can trigger "undefined behavior" (UB). Once UB happens, anything can follow - the correctness of subsequent operation is no longer a guarantee.


In order to safely allow individual items in a C++ collection to be allocated (added) and deallocated (removed), these items will have to be allocated in the heap.

Here, the "heap" refers to the actual heap, i.e. the dynamic memory allocation system in C++. The dynamic memory allocation system allows individual allocations to be requested and relinquished.


Your programming style, which allows you to pre-allocate your vectors up-front, is comparable to the static memory allocation style (on a semantic level).

Static memory allocation style was the norm several decades ago, and is still mandated for certain safety critical systems, such as vehicle electronic control systems for the powerchain and fluidics parts, certain aircraft avionics systems, and weapons systems. However, it is less powerful than dynamic memory allocation style, since static memory allocation style puts a limit on what can be implemented. That said, for applications with well-defined, non-extensible scope (e.g. controlling a specific aspect of a mechanical system, and nothing else, not having to interface with anything else), it is possible to stay with static memory allocation style.


Some of the examples you mentioned are examples of "handle body idiom" (link).

In C++, a handle class allows its users to normally use C++ copy semantics to achieve something similar to C++ pointers and references.

OpenCV's Mat class is a "handle" class. To illustrate this, consider this code snippet:

cv::Mat matOne(cv::Size(640, 480), CV_8UC3);
cv::Mat matTwo = matOne;

After these two lines of code, matTwo and matOne both reference the same object (the matrix or image). This is due to the design and the implementation detail of the cv::Mat class.

If you want to implement a class that behaves similarly, you will need to learn about C++ pointers and references, i.e. the knowledge that you're curious about and felt like missing.


A bit on the "linguistics" of the word "semantics".

In C++, the phrases "copy semantics" and "reference semantics" both refer to aspects of the C++ syntax and its usage. Thus, the use of the word "semantics" is a misnomer, when judged by the standard of the English language.

| improve this answer | |
  • 4
    Actually with UB the correctness of previous operation is also not guaranteed anymore. – lalala May 26 at 11:04
  • 6
    Not sure what you think "semantics" means in English - it means the meaning of something. In computing that's usually equivalent to the observable behaviour. – OrangeDog May 26 at 15:49
  • Not sure where you are getting that OP is pre-allocating vectors. They only say they are avoiding (presumably C style, not std::array) arrays, in favour of std::vector – Caleth May 26 at 17:19
  • 1
    @some_coder here you are: coliru.stacked-crooked.com/a/d338fb9158e6d5df – Ruslan May 27 at 11:36
  • 1
    @some_coder coliru.stacked-crooked.com/a/59cebc4cb71b4bf6 I have personally tested it. In place of causing a UB, I demonstrated that a reallocation has happened, by printing the address before and the address after. Use of the "address before" amounts to a UB. However, a mere reading from an invalid address usually do not reliably cause a crash. In other words, in many situations, UB doesn't crash, but it has the potential to cause silent data corruption in the program. – rwong May 28 at 0:18
6

Between references and std::vec, lots of cases where a C program would use pointers don’t use pointers in C++. And in Swift, you’d need to dig very deep into the standard library to find something that is called “pointer”.

Not using pointers at all is a bit unusual, but quite possible.

| improve this answer | |
  • 2
    Without polymorphism, then. Because squeezing polymorphic objects into a vector tends to lead to object slicing unless you’re really lucky :-) – Christophe May 25 at 20:00
  • @Christophe without subclass polymorphism. You don't need void func(std::vector<ptr<Base>> &) if you can write template <typename T> void func(std::vector<T> &) – Caleth May 26 at 8:45
  • 1
    @Caleth I was referring to run-time polymorphism – Christophe May 26 at 9:11
5

When I need to use an object of a class, I prefer to create the object on the stack and pass it by reference; no new/delete, no smart pointers.

I'm surprised no one actually mentioned, well, you know... stackoverflow! It is rare to have more than 8mb of stack available (AFAIK the default for Linux). It is safe to assume that no more than 1mb is available (AFAIK the default for Windows).

Anyway, there are two main reasons to use heap instead of stack:

  1. An object needs to outlive the function that created it.
  2. An object is too big.

Also you seem to misunderstand something: vectors is cheating! Because a vector allocates, manages and deallocates heap under the hood. It is a very thin wrapper around raw pointer and one may argue that the biggest difference is that vectors additionally have size(s). So it is a sort of smart pointer, right?

| improve this answer | |
  • 3
    OP doesn't use pointers. std uses pointers for them. – Caleth May 26 at 17:15
  • 1
    @Caleth sure, with std::shared_ptr you don't use pointers as well. Std does it for you. – freakish May 26 at 17:51
  • Interesting remark! Could we just use the terminology “auto storage” instead of stack (even if in practice it is indeed implemented mostly on the stack) and “free store” instead of heap ? – Christophe May 27 at 8:33
  • But pointers are resizeable: see "man realloc". – jamesqf May 27 at 21:41
  • @jamesqf true, I've updated the answer. – freakish May 28 at 8:32
4

In addition to Christoph's answer, there are also a few data structures that you cannot easily build and use without pointers:

  • Trees of objects

  • DAGs

  • Linked lists (delegation chains, for example)

Of course, you can work around using pointers for these things. Trees and DAGs can be constructed from objecs stored in an std::vector<>, linking them up by their respective indices within that std::vector<>, and for linked lists, you can simply resort to std::list<>. The later uses pointers under the hood, the former replaces the explicit pointer with an int that serves exactly the same function and suffers from the same problems. Under the hood, pointers are nothing but integer values which are used to address some bytes in memory, so such use of indices is actually as bad as the use of void pointers. Better use typed pointers, smart ones when you need to manage ownership.

How much you need such data structures, and hence pointers, depends heavily on your use cases. So, maybe you simply never had a problem that called for a true delegation chain. For instance, when you run a physics simulation, you simply want some 3D arrays on which you can calculate the next timestep; that does not involve any fancy DAGs. However, if you were programming a distributed version control software, you absolutely should be working with pointers, for the commits form a DAG, each referencing one or more parent commits, branches referencing commits, etc. pp.

In the end, pointers are nothing but a tool. A tool for some specific purposes. Sometimes, a problem calls for this tool, and sometimes it calls for another. It is equally wrong not to use pointers in the former case, as it is wrong to use them in the later.

| improve this answer | |
  • "Trees and DAGs can be constructed from objects stored in a std::vector, linking them up by their respective indices within that vector" — In fact, I often find myself telling people that this is what they SHOULD do, in real codebases! In my experience, people often think "if you were programming a distributed version control software, you absolutely should be working with pointers, for the commits form a DAG," but in fact that's a trap, a pitfall, an antipattern. Use unordered_map<SHA, CommitRecord>. More on the skeuomorphism trap. – Quuxplusone May 26 at 17:05
  • 1
    @Quuxplusone And what exactly do you think you are gaining by using a key instead of a pointer? I have told you why I think that pointers are superior to such keys (because the key lacks a type). But what virtue does the key have in your opinion, that it can offset the loss of a compiler checked type? – cmaster - reinstate monica May 26 at 18:08
  • As Mat's top-level comment says, "You're missing out on all the memory safety bugs, most segfaults, invalid casts, dangling pointer errors, leaks..." In practice, people often try to deal with these by replacing T* with shared_ptr; then they get refcount cycles. It's usually at that point that they ask me what to do to eliminate refcount cycles, and I say "hmm, looks like you should go back to the map<Key, Record> or vector<Record>+integer-index approach." "The model is pointers so let's use shared_ptr<T>" is on par with "The model is books so let's make class Book." – Quuxplusone May 26 at 18:48
  • @Quuxplusone Fair enough, you absolutely need to think about retention cycles when using std::shared_ptr<>. Nevertheless, just as with pointers, your keys can become dangling. If your keys have enough entropy, you can test whether the object still exists before you start perusing it, that is indeed a plus of the hash map approach. However, hash maps are costly where a simple pointer suffices. And you still run the danger of keying into the wrong table with your key because there is no type associated with the key. Of course, you could fix that by using key classes, but honestly, who does? – cmaster - reinstate monica May 26 at 20:54
  • 1
    This is a good point. Worth to mention in the commented discussion: The world is not always best represented with vectors: vector elements need to be contiguous. There are some problems that can indeed simply expressed with indexes. But there are others where it is not necessarily the case (e.g. very large graphs, graphs or graphs, graphs with sorted nodes, etc...). I think a portion of pragmatism is needed: let’s chose the most appropriate language feature for the given problem and not exclude a useful language feature arbitrarily :-) – Christophe May 27 at 8:49
4

The original designer of C++, Bjarne Stroustrup, approves! In his CPP Core Guidelines, he advises all C++ programmers to “prefer scoped objects, don’t heap allocate unnecessarily.” He recommends the use of RAII to manage resources, and says that references should be non-owning. If you’re creating local objects in function scope, on the stack, and passing them to other functions by reference, you’re following the Guidelines already!

There are some important things you can’t do in C++ without some use of pointers. Others have mentioned several. Copy elision covers most of the cases where you would need to return an object from a function, but not all, and not cases where you want to transfer ownership. You can still get many of the uses of polymorphism by passing a function parameter such as &Base supportsTheInterface, but you need some kind of pointer to create a polymorphic data structure. In most large programs, you will want to be able to share data. And you will probably be asked to maintain or interface with code that uses pointers. One other use case that I haven’t seen anyone else bring up yet: smart pointers are a very handy way to move data between objects more efficiently than you could copy it.

| improve this answer | |
2

I bet you're using this pointers implicitly! ;-)

Seriously, C++ is a big language, and most programs don't use all of it. Sean Parent gave a somewhat famous talk suggesting that C++ programmers should never write raw loops and instead should learn to use the algorithms provided by the standard libraries. To many, that may seem more extreme than not using pointers.

C++ has different tools for different jobs. You should use tools like template meta programming, inheritance, lambdas only when you need to, not just because you can. Don't use a pointer when you don't need to.

There are lots of times people use pointers when they don't need to, especially bare metal pointers. Letting libraries like <vector> handle the raw pointers and dynamic allocation is just plain smart. Note that, in a sense, you're still using pointers, you're just relying on code written by other C++ programmers to manage the low-level details so that you can focus on a higher level of abstraction.

Pointers are natural and appropriate for certain kinds of solutions, like linking nodes in a graph. In some solutions, they are essential, like dynamically allocating polymorphic objects. In most cases, you can still avoid the low-level details by relying on smart pointers, which, like std::vector are just classes managing details on your behalf.

At some point, you might have to write your own abstraction class--a specialized container or a new kind of smart pointer. Even Sean Parent acknowledged that you might need a raw loop to implement a new algorithm, but that you should encapsulate it.

When you get to the point that you need to create a pointer-dependent abstraction (or when you have to understand somebody else's code or interface with some library that wants to exchange pointers), you'll want to have had enough experience with pointers that you're equipped for the task.

Pointers (sometimes in the guise of an address, handle, or reference) are pretty fundamental concepts to many programming languages. Don't miss the opportunity to learn about them by contorting your code to avoid them when they are the appropriate tool.

| improve this answer | |
1

When I have to store primitive data, I prefer std::vector over array. When I need to use an object of a class, I prefer to create the object on the stack and pass it by reference; no new/delete, no smart pointers.

Most C++ developers would agree with you on all these points. Proper containers are safer than primitive arrays, and if you can avoid allocating something on the heap then you should.

There are some instances, however, when you do need pointers. If you can avoid them in your code, then that's great, but they become more difficult to avoid in larger projects.

Scope Extrusion

This is a fancy name for a fairly simple concept. The scope of a function is the body of that function together with all the bodies of functions that it calls, the bodies of functions that they call, and so on. If I declare a local variable in a function, then I know that it will stay alive until that function exits. So I can freely pass it (by reference) to any other functions within that function, and know that I'm doing something safe.

But what if I want to create an object inside my function, store it somewhere and have it persist after the function has exited? If we were writing Java, we would not think twice about writing something like this (for example, in a factory class).

public BigObject createBigObject()
{
  BigObject bigObject = new BigObject(param1, param2);
  return bigObject;
}

Even after createBigObject() has run, we want the variable bigObject still to be alive. Since Java is a garbage-collected language, this is fine.

In C++, if we tried to write

BigObject& createBigObject() const
{
  BigObject bigObject(param1, param2);
  return bigObject;
}

then the behaviour of the program is undefined. At best, your compiler will warn you that you are returning a reference to a local variable. If you try to run the code, you might find that it works some times, and crashes at others, since the lifetime of bigObject ends when createBigObject returns. You need to use dynamic allocation, either with new or with a smart pointer. E.g.:

BigObject* createBigObject() const
{
  BigObject* const bigObject = new BigObject(param1, param2);
  return bigObject;
}

If you wanted to, you could return *bigObject instead and return a BigObject& here.

Mutable References

References in C++ are immutable: they have to be initialized to refer to some other object when they are created and cannot then be pointed towards some other object. In that respect, a T& is a bit like a T* const (and a T const& is a bit like a T const* const). Now suppose you are setting up a class and you want to hold a reference to some other object as a member variable in that class. If the other object is all set up when you construct the class then this is fine - you can initialized the reference straight away - but if you have to set things up later in an initialization step, then you'll need to use a pointer so that you can point it at the object only once it's been created.

Polymorphism (sort of)

If you've programmed in Java, you've probably made a lot of use of interfaces. In C++, if you want to do the same thing, you normally need to use pointers (sort of - sometimes you want to use templates instead). In Java, we can write

List<String> list = new ArrayList<String>();

to indicate that we only care that list is some sort of list - the fact that it is an ArrayList is an implementation detail. In C++, writing

List<std::string> list = ArrayList<std::string>();

(supposing that List and ArrayList existed as types in your code) would try to construct a List and then set it equal to the ArrayList, which would be a compile error if List had pure virtual members, since then it would be impossible to create List objects. list would have to be either a List& reference or a List* pointer.

Now sometimes you can use references to do polymorphism (as you could in this example), but in such cases you're usually better off doing compile-time polymorphism using templates. In C++ we usually use runtime polymorphism when we want to store the polymorphic objects in some container (e.g., a std::vector). And this comes back to the previous point: it's impossible to just store a reference on the fly, since references must be initialized when they are created. So we have to store them as pointers.

| improve this answer | |
0

Pointers are a low-level programming mechanism. The "whole point" of programmingcitation needed is to make higher-level mechanisms out of lower-level mechanisms, where, generally, lower-level means more powerful but harder to use, and higher-level means less powerful but easier to use.

So, maybe, generally, pointers make more sense in lower-level code, and less sense in higher-level code.

| improve this answer | |
  • 1
    Do you consider runtime polymorphism as a low level or high level feature ? If high level, can you explain how you’d do a polymorphic container without pointer? (I’d really like to see how you’d put references in a vector in some code) – Christophe May 26 at 21:51
  • 1
    @Christophe I very specifically didn't say "high level code should never use pointers". besides that, what you describe seems to be a limitation of std::vector not runtime polymorphism. in a lower-level language, it may be the case that it's not possible to abstract away literally every last lower-level mechanism, but you can at least try to minimize them. – Dave Cousineau May 26 at 22:04
  • You are right: raw pointers are rather low level and come with a lot of low level hassle, like allocating and deallocating memory, or preventing shallow copies. But what if, instead of looking at the technical implementation, we would look at the high level semantic ? What makes a shared_ptr so much different from a java object reference ? Why the would a java object reference be high level and a shared_ptr low-level, when both offer similar features and one just has « pointer » in its name ? And why minimize a robust and safe feature when it fulfills the needs ? – Christophe May 26 at 22:18
  • @Christophe when I say "pointer" I mean something like int *x = &y;. I'm not a C++ expert, but as far as I know "smart pointers" are already a higher-level mechanism. the point of my answer is to pull the question away from "pointers: y/n" and indicate that IMO generally a programmer should be trying to push their code "up" to higher-level status when possible (and maybe, that is literally what being a programmer is). given that pointers are a lower-level mechnism, then, maybe, they should not be spread haphazardly throughout your code, and there are more and less appropriate places for them – Dave Cousineau May 26 at 22:29
  • understand and do not disagree with the general principle of working at the right level of abstraction and fully agree with your categorisation of smart pointers. The whole point behind my questions is that « high » and « low » seem arbitrary and full of prejudice and on an engineering site deserve a little more precise definition. I believe that bringing a couple of your very clear comments into the answer could significantly improve it; I wouldn’t exclude an upvote in that case :-) – Christophe May 26 at 22:41
0

No, you're not doing anything wrong. Pointers (not references, not reference counters, not unique wrappers) are a concept that is used to model the location of a value in a given memory address space. So:

  • If the domain of your program is closely related to how things are laid out in memory (e.g. you're writing a memory allocator or something like that), then pointers will come up sooner or later.
  • Otherwise (and this is the most likely scenario) it's perfectly normal for pointers to not come up.
    • Unless you're using a language that has no other way to express a reference, such as C. In that case, pointers will come up regardless of your domain. But this is why people typically don't use C for programs that don't operate in that domain.
| improve this answer | |
  • with "reference counters" and "unique wrappers", fo you mean std_shared and std_unique ? And what do you think about iterators ? – Christophe May 26 at 21:48
0

Define wrong. If your code runs correctly, then in a simplistic sense it is not wrong.

However, by not using pointers at all, you are creating a number of potential problems for yourself.

1) If you never use pointers, you will never understand them well. Then what will you do when you have to understand or interface with code written by someone who does use pointers?

2) Pointers are (IMHO - individual opinions may vary) often much easier to understand, and to code with, than the alternatives you're using.

3) Pointers can often (though of course not always) be more efficient than some of those alternatives, so if your goal is to write performance-critical code, you've handicapped yourself.

| improve this answer | |

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