In my experience, basic 2D grid maps are usually done like this:
A map may consist of multiple layers -- but no fewer than 1 layer (multiple layers can allow for parallax scrolling effects, or overlays drawn above the player)
Each layer has at least 2 things: an assigned "tileset", and a 2D grid of tile indexes.
Each tileset is assigned a graphic/texture, as well as any interactive properties of the tiles it contains (ie: is the tile open space or a wall?)
Implementation-wise, I've found it's easiest to load up the tileset into a vector of individual Tile properties. Then the map layers contain a simulated 2D array of pointers to tiles within that tileset vector. The pitfall with this is that the pointers have to stay valid throughout the lifetime of the map, which means the Tiles themselves cannot move in memory... and the Tileset must remain alive for as long as any layers are using them.
The "clip" as you call them can be specified in the tileset information if you need it to be fancy... or if not, you can just have the tile index itself double as the graphic location.
For example, if your graphic has 10 tiles per row.... then tile 0 would be the upper left corner of the graphic, tile 1 would be to its right, etc. Tile 10 would be the left-most graphic on the 2nd row from the top, etc.
Tiles do not need to contain a X,Y coordinate for where they are on the map, as that information is already inferred by their position in the map's 2D array.
static const int width = ...;
static const int height = ...;
//... any other properties you want tiles to have go here
for(int i = 0; ...each tile in file...; ++i)
t.graphicX = (i % tiles_per_row) * Tile::width;
t.graphicY = (i / tiles_per_row) * Tile::height;
//... load other properties from the tileset here
// remove move/copy ctors and assignment operators to prevent this class from moving
// around, as that will cause the pointers to go bad.
std::vector<const Tile*> tiles;
void load(const Tileset& tileset)
for( ... each tile in the layer ... )
i = read_a_tile_index_from_the_file;
tiles.push_back( tileset.getTile( i ) ); // where 'getTile' returns a pointer to the tile
std::vector< std::unique_ptr<Tileset> > tilesets;
// The tileset allocated dynamically so it doesn't move around in memory
Take from that what you what