5

In our project code, a resource type should not be copied.

struct Res {
    ....
    Res(const Res& rhs) = delete;
    Res& operator=(const Res&) = delete;
    Res(Res&&) = delete;
    Res& operator=(Res&&) = delete;
};

However, seems std container all require copy constructor. So how could I store the Res in std::vector?

class ResourceCache{
    ....
    const Res& GetRes(size_t index);
    std::vector<Res> resources_;
}
heLomaN
  • 1,634
  • 2
  • 22
  • 33
  • 2
    You just answered your own question: you can't. But you can, for example, store a `std::shared_ptr` or a `std::unique_ptr` in the vector. If all instances of your class get created in dynamic scope, you must already be using smart pointers, right? If so, just store them in the vector also. Problem solved. – Sam Varshavchik Nov 24 '18 at 03:07
  • 1
    Can the object be moved? – NathanOliver Nov 24 '18 at 03:13
  • As explicitly shown in the question, the object cannot be moved either, so this is not a dupe of that question, but, whatever... – Sam Varshavchik Nov 24 '18 at 03:14
  • 1
    emplace_back will construct the object directly in the container. – Michael Surette Nov 24 '18 at 03:24
  • @MichaelSurette: That would require the `vector` to `reserve` space once up front, and never resize again though; otherwise, a capacity change would force moving elements from old to new storage, which would fail. C++11 and later allow it theoretically for `vector`s in general, but mention that most methods impose stricter requirements. – ShadowRanger Nov 24 '18 at 03:27
  • @SamVarshavchik Just as you said, the main concern is that use `std::unique_ptr` or `std::shared_ptr` will lead to more cache miss. – heLomaN Nov 24 '18 at 05:02
  • I see. This is one of those situations where every last picosecond counts. Glad I don't work in that industry any more. Well, I guess you can't do this, at all. – Sam Varshavchik Nov 24 '18 at 14:28

1 Answers1

2

Since you've deleted both copy and move constructors/assignment operators, the answer is: You can't make use of such a vector in any reasonable way. Without copy or move as an option, you're at best able to pre-reserve space once and emplace_back into the vector, but anything that might subsequently change the capacity, or in any way rearrange the elements, would be illegal (because it would implicitly involve use of move or copy operations).

As mentioned in the comments though, you can make a vector of smart pointers, where only the smart pointer must be moved/copied, not the Res instance it points to, and get a fully functional vector. For example, with std::unique_ptr, your class could be implemented as:

class ResourceCache{
    ....
    const Res& GetRes(size_t index) {
        return *resources_.at(index);  // Can still provide const references at API level
    }
    std::vector<std::unique_ptr<Res>> resources_;
}

and you'd just use resources_.push_back(std::make_unique<Res>(...args to Res constructor...)) to create/insert elements into resources_, rather than resources_.push_back(Res(...args to Res constructor...)).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • For about 100,000 objects and frequently call of `GetRes`, I guess dereference pointer will cost a lot. Will `std::array` work well without copy constructor? Or should I malloc a big block memory with emplacement new? – heLomaN Nov 24 '18 at 05:39
  • @heLomaN: Don't guess; profile. I suspect you'll find your guess is wrong. `GetRes` returns a reference, which for most purposes functions like a pointer (on all compilers I know of), so `return *resources_.at(index);` won't actually deference the pointer at the time you return, just convert it to a reference. While your `Res` objects won't be contiguous in memory (which *might* increase cache misses), otherwise the behavior will be the same, with the same number of indirect lookups as before. I'd strongly recommend trying it with `std::unique_ptr` before assuming it will be too slow. – ShadowRanger Nov 24 '18 at 05:56
  • @heLomaN: If that really does become a problem, `std::vector` with an initial `reserve` followed by `emplace_back` might work (`std::array` is inappropriate since it only initializes via aggregate initialization, which makes dynamic filling a problem). But failing that, you might be stuck with `::operator new` to get the memory block, then placement new to fill it. – ShadowRanger Nov 24 '18 at 06:03
  • 1
    `emplace_back()` will not work. Even though there's pre-reserved space, `emplace_back()` will still generate code to reallocate the vector. A random `emplace_back()` in some translation unit does not have knowledge that it will always have reserved space available, and, as such, will need to generate reallocation code for that possibility, which will fail here because the class cannot be copied/moved. Try for yourself. It won't work. – Sam Varshavchik Nov 24 '18 at 14:27
  • @SamVarshavchik: Thanks. I figured that was a possibility (thus qualifying with "might"). Guessing the OP's goal isn't really possible with the STL as specified. – ShadowRanger Nov 24 '18 at 15:12